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