1 module glamour.shader; 2 3 private { 4 import glamour.gl : GLenum, GLuint, GLint, GLchar, GLboolean, 5 GL_VERTEX_SHADER, 6 // GL_TESS_CONTROL_SHADER, GL_TESS_EVALUATION_SHADER, 7 GL_GEOMETRY_SHADER, GL_FRAGMENT_SHADER, 8 GL_LINK_STATUS, GL_FALSE, GL_INFO_LOG_LENGTH, 9 GL_COMPILE_STATUS, GL_TRUE, 10 glCreateProgram, glCreateShader, glCompileShader, 11 glLinkProgram, glGetShaderiv, glGetShaderInfoLog, 12 glGetProgramInfoLog, glGetProgramiv, glShaderSource, 13 glUseProgram, glAttachShader, glGetAttribLocation, 14 glDeleteProgram, glDeleteShader, glGetFragDataLocation, 15 glGetUniformLocation, glUniform1i, glUniform1f, 16 glUniform2f, glUniform2fv, glUniform3fv, 17 glUniform4fv, glUniformMatrix2fv, glUniformMatrix2x3fv, 18 glUniformMatrix2x4fv, glUniformMatrix3fv, glUniformMatrix3x2fv, 19 glUniformMatrix3x4fv, glUniformMatrix4fv, glUniformMatrix4x2fv, 20 glUniformMatrix4x3fv, glUniform2iv, glUniform3iv, glUniform4iv; 21 import glamour.util : checkgl; 22 23 import std.conv : to; 24 import std.file : readText; 25 import std.path : baseName, stripExtension; 26 import std..string : format, splitLines, toStringz, toLower, strip; 27 import std.array : join, split; 28 import std.algorithm : startsWith, endsWith; 29 import std.exception : enforceEx; 30 import std.typecons : Tuple; 31 32 version(gl3n) { 33 import gl3n.util : is_vector, is_matrix, is_quaternion; 34 } 35 36 debug import std.stdio : stderr; 37 } 38 39 40 GLenum to_opengl_shader(string s, string filename="<unknown>") { 41 switch(s) { 42 case "vertex": return GL_VERTEX_SHADER; 43 case "geometry": return GL_GEOMETRY_SHADER; 44 case "fragment": return GL_FRAGMENT_SHADER; 45 default: throw new ShaderException(format("Unknown shader, %s.", s), "load", filename); 46 } 47 assert(0); 48 } 49 50 51 // enum ctr_shader_type = ctRegex!(`^(\w+):`); 52 53 /// This exception will be raised when 54 /// an error occurs while compiling or linking a shader. 55 class ShaderException : Exception { 56 /// The filename passed to the ctor. 57 string filename; 58 /// The process passed to the ctor. Will be one of "linking" or "compiling". 59 string process; 60 61 /// Params: 62 /// infolog = Infolog returned from OpenGL. 63 /// process_ = Error occured while linking or compiling? 64 /// filename_ = Used to identify the shader. 65 this(string infolog, string process_, string filename_="<unknown>") { 66 filename = filename_; 67 process = process_; 68 69 infolog ~= "\nFailed to " ~ process_ ~ " shader: " ~ filename_ ~ ". "; 70 71 super(infolog); 72 } 73 } 74 deprecated alias ShaderException ShaderError; 75 76 /// Compiles an already created OpenGL shader. 77 /// Throws: ShaderException on failure. 78 /// Params: 79 /// shader = The OpenGL shader. 80 /// filename = Used to identify the shader, if an error occurs. 81 void compile_shader(GLuint shader, string filename="<unknown>") { 82 checkgl!glCompileShader(shader); 83 84 GLint status; 85 checkgl!glGetShaderiv(shader, GL_COMPILE_STATUS, &status); 86 if(status == GL_FALSE) { 87 GLint infolog_length; 88 checkgl!glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infolog_length); 89 90 GLchar[] infolog = new GLchar[infolog_length+1]; 91 checkgl!glGetShaderInfoLog(shader, infolog_length, null, infolog.ptr); 92 93 throw new ShaderException(infolog.to!string(), "link", filename); 94 } 95 } 96 97 /// Links an already created OpenGL program. 98 /// Throws: ShaderException on failure. 99 /// Params: 100 /// program = The OpenGL program. 101 /// filename = Used to identify the shader, if an error occurs. 102 void link_program(GLuint program, string filename="<unknown>") { 103 checkgl!glLinkProgram(program); 104 105 GLint status; 106 checkgl!glGetProgramiv(program, GL_LINK_STATUS, &status); 107 if(status == GL_FALSE) { 108 GLint infolog_length; 109 checkgl!glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infolog_length); 110 111 GLchar[] infolog = new GLchar[infolog_length + 1]; 112 checkgl!glGetProgramInfoLog(program, infolog_length, null, infolog.ptr); 113 114 throw new ShaderException(infolog.to!string(), "compile", filename); 115 } 116 } 117 118 /// Stores each line of the shader, line and text. 119 alias Tuple!(size_t, "line", string, "text") Line; 120 121 /// Represents an OpenGL program with it's shaders. 122 /// The constructor must be used to avoid segmentation faults. 123 class Shader { 124 /// The OpenGL program. 125 GLuint program; 126 /// Alias this to program. 127 alias program this; 128 129 private GLuint[] _shaders; 130 /// Holds every shaders source. 131 Line[][string] shader_sources; 132 /// Holds the directives. 133 string[] directives; 134 135 /// The shaders filename. 136 string filename; 137 138 /// Uniform locations will be cached here. 139 GLint[string] uniform_locations; 140 /// Attrib locations will be cached here. 141 GLint[string] attrib_locations; 142 /// Frag-data locations will be cached here. 143 GLint[string] frag_locations; 144 145 /// Loads the shaders directly from a file. 146 this(string file) { 147 this(stripExtension(baseName(file)), readText(file)); 148 } 149 150 /// Loads the shader from the source, 151 /// filename_ is stored in $(I filename) and will be used to identify the shader. 152 this(string filename_, string source) { 153 filename = filename_; 154 155 program = checkgl!glCreateProgram(); 156 157 Line[]* current; 158 foreach(size_t line, string text; source.splitLines()) { 159 if(text.startsWith("#")) { 160 directives ~= text; 161 } else { 162 auto m = text.strip().split(); 163 164 if(m.length >= 1 && m[0].endsWith(":")) { 165 string type = toLower(m[0][0..$-1]); 166 shader_sources[type] = null; 167 current = &(shader_sources[type]); 168 } else { 169 if(current !is null) { 170 *current ~= Line(line, text); 171 } 172 } 173 } 174 } 175 176 if(!directives.length) { 177 // OSX only supports 3.2 forward contexts 178 version(OSX) { 179 directives ~= "#version 150\n"; 180 } else { 181 directives ~= "#version 130\n"; 182 } 183 } 184 185 foreach(string type, Line[] lines; shader_sources) { 186 string shader_source = directives.join("\n") ~ "\n\n"; 187 188 foreach(Line line; lines) { 189 shader_source ~= format("#line %d\n%s\n", line.line, line.text); 190 } 191 192 GLenum shader_type = to_opengl_shader(type, filename); 193 GLuint shader = checkgl!glCreateShader(shader_type); 194 auto ssp = shader_source.ptr; 195 int ssl = cast(int)(shader_source.length); 196 checkgl!glShaderSource(shader, 1, &ssp, &ssl); 197 198 compile_shader(shader, filename); 199 200 _shaders ~= shader; 201 checkgl!glAttachShader(program, shader); 202 } 203 204 link_program(program, filename); 205 } 206 207 ~this() { 208 debug if(program != 0) stderr.writefln("OpenGL: Shader resources not released."); 209 } 210 211 /// Deletes all shaders and the program. 212 void remove() { 213 foreach(GLuint shader; _shaders) { 214 checkgl!glDeleteShader(shader); 215 } 216 _shaders = []; 217 218 checkgl!glDeleteProgram(program); 219 program = 0; 220 } 221 222 /// Binds the program. 223 void bind() { 224 checkgl!glUseProgram(program); 225 } 226 227 /// Unbinds the program. 228 void unbind() { 229 checkgl!glUseProgram(0); 230 } 231 232 /// Queries an attrib location from OpenGL and caches it in $(I attrib_locations). 233 /// If the location was already queried the cache is returned. 234 GLint get_attrib_location(string name) { 235 if(auto loc = name in attrib_locations) { 236 return *loc; 237 } 238 239 debug { 240 auto loc = checkgl!glGetAttribLocation(program, toStringz(name)); 241 if(loc < 0) { 242 stderr.writefln(`glGetAttribLocation returned a value < 0 for location: "%s"`, name); 243 } 244 245 return attrib_locations[name] = loc; 246 } else { 247 return attrib_locations[name] = checkgl!glGetAttribLocation(program, toStringz(name)); 248 } 249 } 250 251 /// Queries an fragment-data location from OpenGL and caches it in $(I frag_locations). 252 /// If the location was already queried the cache is returned. 253 GLint get_frag_location(string name) { 254 if(auto loc = name in frag_locations) { 255 return *loc; 256 } 257 258 debug { 259 auto loc = checkgl!glGetFragDataLocation(program, toStringz(name)); 260 if(loc < 0) { 261 stderr.writefln(`glGetFragDataLocation returned a value < 0 for location: "%s"`, name); 262 } 263 264 return frag_locations[name] = loc; 265 } else { 266 return frag_locations[name] = checkgl!glGetFragDataLocation(program, toStringz(name)); 267 } 268 } 269 270 /// Queries an uniform location from OpenGL and caches it in $(I uniform_locations). 271 /// If the location was already queried the cache is returned. 272 GLint get_uniform_location(string name) { 273 if(auto loc = name in uniform_locations) { 274 return *loc; 275 } 276 277 debug { 278 auto loc = checkgl!glGetUniformLocation(program, toStringz(name)); 279 if(loc < 0) { 280 stderr.writefln(`glGetUniformLocation returned a value < 0 for location: "%s"`, name); 281 } 282 283 return uniform_locations[name] = loc; 284 } else { 285 return uniform_locations[name] = checkgl!glGetUniformLocation(program, toStringz(name)); 286 } 287 } 288 289 // gl3n integration 290 version(gl3n) { 291 /// If glamour gets compiled with version=gl3n support for 292 /// vectors, matrices and quaternions is added 293 void uniform(T)(string name, T value) if(is_vector!T) { 294 static if(is(T.vt : int)) { 295 static if(T.dimension == 2) { 296 checkgl!glUniform2iv(get_uniform_location(name), 1, value.value_ptr); 297 } else static if(T.dimension == 3) { 298 checkgl!glUniform3iv(get_uniform_location(name), 1, value.value_ptr); 299 } else static if(T.dimension == 4) { 300 checkgl!glUniform4iv(get_uniform_location(name), 1, value.value_ptr); 301 } else static assert(false); 302 } else { 303 static if(T.dimension == 2) { 304 checkgl!glUniform2fv(get_uniform_location(name), 1, value.value_ptr); 305 } else static if(T.dimension == 3) { 306 checkgl!glUniform3fv(get_uniform_location(name), 1, value.value_ptr); 307 } else static if(T.dimension == 4) { 308 checkgl!glUniform4fv(get_uniform_location(name), 1, value.value_ptr); 309 } else static assert(false); 310 } 311 } 312 313 /// ditto 314 void uniform(S : string, T)(S name, T value) if(is_matrix!T) { 315 static if((T.rows == 2) && (T.cols == 2)) { 316 checkgl!glUniformMatrix2fv(get_uniform_location(name), 1, GL_TRUE, value.value_ptr); 317 } else static if((T.rows == 3) && (T.cols == 3)) { 318 checkgl!glUniformMatrix3fv(get_uniform_location(name), 1, GL_TRUE, value.value_ptr); 319 } else static if((T.rows == 4) && (T.cols == 4)) { 320 checkgl!glUniformMatrix4fv(get_uniform_location(name), 1, GL_TRUE, value.value_ptr); 321 } else static if((T.rows == 2) && (T.cols == 3)) { 322 checkgl!glUniformMatrix2x3fv(get_uniform_location(name), 1, GL_TRUE, value.value_ptr); 323 } else static if((T.rows == 3) && (T.cols == 2)) { 324 checkgl!glUniformMatrix3x2fv(get_uniform_location(name), 1, GL_TRUE, value.value_ptr); 325 } else static if((T.rows == 2) && (T.cols == 4)) { 326 checkgl!glUniformMatrix2x4fv(get_uniform_location(name), 1, GL_TRUE, value.value_ptr); 327 } else static if((T.rows == 4) && (T.cols == 2)) { 328 checkgl!glUniformMatrix4x2fv(get_uniform_location(name), 1, GL_TRUE, value.value_ptr); 329 } else static if((T.rows == 3) && (T.cols == 4)) { 330 checkgl!glUniformMatrix3x4fv(get_uniform_location(name), 1, GL_TRUE, value.value_ptr); 331 } else static if((T.rows == 4) && (T.cols == 3)) { 332 checkgl!glUniformMatrix4x3fv(get_uniform_location(name), 1, GL_TRUE, value.value_ptr); 333 } else static assert(false, "Can not upload type " ~ T.stringof ~ " to GPU as uniform"); 334 } 335 336 /// ditto 337 void uniform(S : string, T)(S name, T value) if(is_quaternion!T) { 338 checkgl!glUniform4fv(get_uniform_location(name), 1, value.value_ptr); 339 } 340 } else { 341 void uniform(S, T)(S name, T value) { 342 static assert(false, "you have to compile glamour with version=gl3n to use Shader.uniform"); 343 } 344 } 345 346 /// Sets a shader uniform. Consider the corresponding OpenGL for more information. 347 void uniform1i(string name, int value) { 348 checkgl!glUniform1i(get_uniform_location(name), value); 349 } 350 351 /// ditto 352 void uniform1i(GLint name, int value) { 353 checkgl!glUniform1i(name, value); 354 } 355 356 /// ditto 357 void uniform1f(string name, float value) { 358 checkgl!glUniform1f(get_uniform_location(name), value); 359 } 360 361 /// ditto 362 void uniform1f(GLint name, float value) { 363 checkgl!glUniform1f(name, value); 364 } 365 366 /// ditto 367 void uniform2f(string name, float value1, float value2) { 368 checkgl!glUniform2f(get_uniform_location(name), value1, value2); 369 } 370 371 /// ditto 372 void uniform2f(GLint name, float value1, float value2) { 373 checkgl!glUniform2f(name, value1, value2); 374 } 375 376 /// ditto 377 void uniform2fv(string name, const float[] value) { 378 checkgl!glUniform2fv(get_uniform_location(name), cast(int)(value.length/2), value.ptr); 379 } 380 381 /// ditto 382 void uniform2fv(GLint name, const float[] value) { 383 checkgl!glUniform2fv(name, cast(int)(value.length/2), value.ptr); 384 } 385 386 /// ditto 387 void uniform2fv(string name, const float[] value, int count) { 388 checkgl!glUniform2fv(get_uniform_location(name), count, value.ptr); 389 } 390 391 /// ditto 392 void uniform2fv(GLint name, const float[] value, int count) { 393 checkgl!glUniform2fv(name, count, value.ptr); 394 } 395 396 /// ditto 397 void uniform3fv(string name, const float[] value) { 398 checkgl!glUniform3fv(get_uniform_location(name), cast(int)(value.length/3), value.ptr); 399 } 400 401 /// ditto 402 void uniform3fv(string name, const float[] value, int count) { 403 checkgl!glUniform3fv(get_uniform_location(name), count, value.ptr); 404 } 405 406 /// ditto 407 void uniform3fv(GLint name, const float[] value, int count) { 408 checkgl!glUniform3fv(name, count, value.ptr); 409 } 410 411 /// ditto 412 void uniform4fv(string name, const float[] value) { 413 checkgl!glUniform4fv(get_uniform_location(name), cast(int)(value.length/4), value.ptr); 414 } 415 416 /// ditto 417 void uniform4fv(GLint name, const float[] value) { 418 checkgl!glUniform4fv(name, cast(int)(value.length/4), value.ptr); 419 } 420 421 /// ditto 422 void uniform4fv(string name, const float[] value, int count) { 423 checkgl!glUniform4fv(get_uniform_location(name), count, value.ptr); 424 } 425 426 /// ditto 427 void uniform4fv(GLint name, const float[] value, int count) { 428 checkgl!glUniform4fv(name, count, value.ptr); 429 } 430 431 /// ditto 432 void uniform_matrix3fv(string name, const float[] value, GLboolean transpose=GL_TRUE) { 433 checkgl!glUniformMatrix3fv(get_uniform_location(name), cast(int)(value.length/9), transpose, value.ptr); 434 } 435 436 /// ditto 437 void uniform_matrix3fv(GLint name, const float[] value, GLboolean transpose=GL_TRUE) { 438 checkgl!glUniformMatrix3fv(name, cast(int)(value.length/9), transpose, value.ptr); 439 } 440 441 /// ditto 442 void uniform_matrix3fv(string name, const float[] value, GLboolean transpose=GL_TRUE, int count=1) { 443 checkgl!glUniformMatrix3fv(get_uniform_location(name), count, transpose, value.ptr); 444 } 445 446 /// ditto 447 void uniform_matrix3fv(GLint name, const float[] value, GLboolean transpose=GL_TRUE, int count=1) { 448 checkgl!glUniformMatrix3fv(name, count, transpose, value.ptr); 449 } 450 451 /// ditto 452 void uniform_matrix4fv(string name, const float[] value, GLboolean transpose=GL_TRUE) { 453 checkgl!glUniformMatrix4fv(get_uniform_location(name), cast(int)(value.length/16), transpose, value.ptr); 454 } 455 456 /// ditto 457 void uniform_matrix4fv(GLint name, const float[] value, GLboolean transpose=GL_TRUE) { 458 checkgl!glUniformMatrix4fv(name, cast(int)(value.length/16), transpose, value.ptr); 459 } 460 461 /// ditto 462 void uniform_matrix4fv(string name, const float[] value, GLboolean transpose=GL_TRUE, int count=1) { 463 checkgl!glUniformMatrix4fv(get_uniform_location(name), count, transpose, value.ptr); 464 } 465 466 /// ditto 467 void uniform_matrix4fv(GLint name, const float[] value, GLboolean transpose=GL_TRUE, int count=1) { 468 checkgl!glUniformMatrix4fv(name, count, transpose, value.ptr); 469 } 470 }