1 module moggle.core.shader; 2 3 import std.conv; 4 import std.file; 5 import moggle.core.gl; 6 7 /// The type of a Shader. 8 enum ShaderType : GLenum { 9 vertex = GL_VERTEX_SHADER, /// A vertex shader (GL_VERTEX_SHADER). 10 fragment = GL_FRAGMENT_SHADER /// A fragment shader (GL_FRAGMENT_SHADER). 11 } 12 13 /++ An OpenGL shader. 14 15 This is a wrapper around a GLuint created by glCreateShader(type). 16 +/ 17 struct Shader { 18 19 private GLuint id_ = 0; 20 21 /// Ths id of this Shader, or 0 if it is not yet created in OpenGL. 22 @property GLuint id() const { return id_; } 23 24 /// Check if this Shader is already created in OpenGL. 25 @property bool created() const { return id != 0; } 26 /// ditto 27 bool opCast(T : bool)() const { return created; } 28 29 @disable this(this); 30 31 /++ Create a shader of the specified type. 32 33 If the shader is already created, nothing will happen 34 if it is of the same type, or it is destroyed and (re)created 35 if it is of a different type. 36 +/ 37 this(ShaderType t) { create(t); } 38 39 /// ditto 40 void create(ShaderType t) { 41 if (id_ && type() != t) destroy(); 42 if (!id_) id_ = glCreateShader(t); 43 } 44 45 /// Create, load, and compile a shader directly from source code. 46 static Shader fromSource(ShaderType t, in char[] source_code) { 47 auto s = Shader(t); 48 s.load(source_code); 49 s.compile(); 50 return s; 51 } 52 53 /// Create, load, and compile a shader directly from a file. 54 static Shader fromFile(ShaderType t, in char[] file_name) { 55 auto s = Shader(t); 56 s.load(cast(char[])read(file_name)); 57 s.compile(); 58 return s; 59 } 60 61 /// Destroy the OpenGL Shader and reset the id back to 0. 62 void destroy() { 63 glDeleteShader(id_); 64 id_ = 0; 65 } 66 67 /// ditto 68 ~this() { destroy(); } 69 70 /// The type of this shader. 71 ShaderType type() const { 72 GLint t; 73 glGetShaderiv(id, GL_SHADER_TYPE, &t); 74 return cast(ShaderType)t; 75 } 76 77 /// Load the source code for the shader. 78 void load(in char[] source_code) { 79 const char* s = source_code.ptr; 80 const GLint n = cast(int)source_code.length; 81 glShaderSource(id_, 1, &s, &n); 82 } 83 84 /// Try to compile the Shader, check compiled() to see if it succeeded. 85 void try_compile() { 86 glCompileShader(id_); 87 } 88 89 /++ Try to compile, and throw if it didn't succeed. 90 91 Throws: ShaderCompilationError containing the error log on failure. 92 +/ 93 void compile() { 94 try_compile(); 95 if (!compiled()) throw new ShaderCompilationError("glCompileShader: Unable to compile shader:\n" ~ log()); 96 } 97 98 /// Check if the Shader is succesfully compiled. 99 bool compiled() const { 100 GLint status; 101 glGetShaderiv(id, GL_COMPILE_STATUS, &status); 102 return status != GL_FALSE; 103 } 104 105 /// The log of errors for when compilation fails. 106 string log() const { 107 GLint log_size; 108 glGetShaderiv(id, GL_INFO_LOG_LENGTH, &log_size); 109 auto log = new char[log_size]; 110 if (log_size) glGetShaderInfoLog(id, log_size, null, log.ptr); 111 return cast(string)log; 112 } 113 114 } 115 116 /++ An OpenGL shader program. 117 118 This is a wrapper around a GLuint created by glCreateProgram(). 119 Initially, this id is 0. 120 glCreateProgram is automatically called the first time anything is done with the object. 121 +/ 122 struct ShaderProgram { 123 124 private GLuint id_ = 0; 125 126 /// Ths id of this ShaderProgram, or 0 if it is not yet created in OpenGL. 127 @property GLuint id() const { return id_; } 128 129 /// Check if this ShaderProgram is already created in OpenGL. 130 @property bool created() const { return id != 0; } 131 /// ditto 132 bool opCast(T : bool)() const { return created; } 133 134 @disable this(this); 135 136 /// Force the creation of a OpenGL ShaderProgram, or do nothing if already created. (Calls glCreateProgram.) 137 void create() { 138 if (!id_) id_ = glCreateProgram(); 139 assert(id_, "glCreateProgram did not generate a shader program."); 140 } 141 142 /// Destroy the OpenGL Vao and reset the id back to 0. (Calls glDeleteProgram.) 143 void destroy() { 144 glDeleteProgram(id_); 145 id_ = 0; 146 } 147 148 /// ditto 149 ~this() { destroy(); } 150 151 /// destroy() and create(). 152 void clear() { 153 destroy(); 154 create(); 155 } 156 157 /// Attach a Shader to this program. (Calls glAttachShader.) 158 void attach(ref const Shader shader) { 159 create(); 160 glAttachShader(id_, shader.id); 161 } 162 163 /// Bind an attribute name to a location. (Calls glBindAttribLocation.) 164 void bindAttribute(GLuint attribute, const(char)[] name) { 165 name ~= '\0'; 166 create(); 167 glBindAttribLocation(id_, attribute, name.ptr); 168 } 169 170 /// Try to link the ShaderProgram, check linked() to see if it succeeded. (Calls glLinkProgram.) 171 void tryLink() { 172 glLinkProgram(id_); 173 } 174 175 /++ Try to link, and throw if it didn't succeed. 176 177 Throws: ShaderCompilationError containing the error log on failure. 178 +/ 179 void link() { 180 tryLink(); 181 if (!linked()) throw new ShaderCompilationError("glLinkProgram: Unable to link program:\n" ~ log()); 182 } 183 184 /// Check if the ShaderProgram is succesfully linked. (Calls glGetProgramiv with GL_LINK_STATUS.) 185 bool linked() const { 186 GLint status; 187 glGetProgramiv(id, GL_LINK_STATUS, &status); 188 return status != GL_FALSE; 189 } 190 191 /// The log of errors for when linking fails. (Calls glGetProgramInfoLog.) 192 string log() const { 193 GLint log_size; 194 glGetProgramiv(id, GL_INFO_LOG_LENGTH, &log_size); 195 auto log = new char[log_size]; 196 if (log_size) glGetProgramInfoLog(id, log_size, null, log.ptr); 197 return cast(string)log; 198 } 199 200 /// Use this ShaderProgram. (Calls glUseProgram.) 201 void use() { 202 glUseProgram(id_); 203 } 204 205 /// Look up a uniform variable by its name. (Calls glGetUniformLocation.) 206 Uniform!T uniform(T)(const(char)* name) { 207 create(); 208 return Uniform!T(glGetUniformLocation(id_, name)); 209 } 210 211 } 212 213 /++ OpenGL uniform variable. 214 215 This is a wrapper around a GLuint generated by glGetUniformLocation(program, name). 216 217 An object of this type is returned by ShaderProgram.uniform(name). 218 +/ 219 struct Uniform(T) { 220 221 private GLuint id_ = 0; 222 223 /// The location of this Uniform. 224 @property GLuint location() const { return id_; } 225 226 this(GLuint id) { id_ = id; } 227 228 /++ Calls the correct glUniform function, based on the type T. 229 230 (It calls one of: 231 glUniform1f, glUniform1i, glUniform1ui, glUniform1fv, glUniform2fv, 232 glUniform3fv, glUniform4fv, glUniform1iv, glUniform2iv, glUniform3iv, 233 glUniform4iv, glUniform1uiv, glUniform2uiv, glUniform3uiv, glUniform4uiv, 234 glUniformMatrix2fv, glUniformMatrix3fv, glUniformMatrix4fv, 235 glUniformMatrix3x2fv, glUniformMatrix2x3fv, glUniformMatrix4x2fv, 236 glUniformMatrix2x4fv, glUniformMatrix4x3fv, and glUniformMatrix3x4fv.) 237 238 Works for GLfloat, GLint, GLuint, Matrices/Vectors/HVectors of GLfloat, 239 both static and dynamic arrays of GLfloat, GLint and GLuint, 240 and both static and dynamic arrays of Matrices/Vectors/Hvectors of GLfloat. 241 +/ 242 void set(in T value) { 243 244 static if (is(T == T2[], T2)) { 245 size_t length = value.length; 246 } else static if (!is(T == T2[length], T2, size_t length)) { 247 alias T T2; 248 enum length = 1; 249 } 250 251 static if (!is(T2 == Matrix!(base_type, height, width), base_type, size_t height, size_t width)) { 252 enum width = 1; 253 static if (!is(T2 == HVector!(base_type, height), base_type, size_t height)) { 254 enum height = 1; 255 alias T2 base_type; 256 } 257 } 258 259 static if (is(base_type == GLfloat)) enum type_name = "f"; 260 else static if (is(base_type == GLint )) enum type_name = "i"; 261 else static if (is(base_type == GLuint )) enum type_name = "ui"; 262 else static assert(false, "This is not a valid type for glUniform."); 263 264 static if (is(T == base_type)) { // e.g. T == float, T == int 265 mixin("glUniform1" ~ type_name ~ "(id_, value);"); // e.g. glUniform1f 266 } else { 267 static if (is(T == T2) || is(T2 == base_type)) { // e.g. T == float[3], T == int[], T == Matrix!(int, 3) 268 auto ptr = value.ptr; 269 } else { // e.g. T == Matrix!(int, 3)[], T == Vector!(float, 2)[3] 270 auto ptr = value[0].ptr; 271 } 272 static if (width == 1 || height == 1) { 273 mixin("glUniform" ~ to!string(height * width) ~ type_name ~ "v(id_, cast(GLint)length, ptr);"); // e.g. glUniform3fv 274 } else { 275 mixin( 276 "glUniformMatrix" ~ to!string(width) ~ (width == height ? "" : "x" ~ to!string(height)) ~ 277 type_name ~ "v(id_, cast(GLint)length, GL_TRUE, ptr);" 278 ); // e.g. glUniformMatrix2x3fv 279 } 280 } 281 282 } 283 284 } 285 286 /// The error that is thrown when Shader.compile() or ShaderProgram.link() fail. 287 class ShaderCompilationError : Exception { 288 this(string what, string file = __FILE__, size_t line = __LINE__) { 289 super(what, file, line); 290 } 291 }; 292