1 module meld.shader;
2 
3 import derelict.opengl3.gl3;
4 import std..string;
5 import std.file;
6 import std.stdio;
7 import std.ascii : newline;
8 import meld.maths;
9 import meld.texture;
10 import std.typecons;
11 import std.conv;
12 
13 class Shader
14 {
15 private:
16 	enum ShaderType
17 	{
18 		None,
19 		Vertex,
20 		Pixel
21 	}
22 
23 	struct TextureUnit
24 	{
25 		this(GLint textureUnit, Texture texture)
26 		{
27 			this.textureUnit = textureUnit;
28 			this.texture = texture;
29 		}
30 
31 		GLint textureUnit;
32 		Texture texture;
33 	};
34 
35 	GLuint m_program, m_pixelShader = 0, m_vertexShader = 0;
36 	static TextureUnit[string] m_textures;
37 	static GLuint s_currentShader = 0;
38 
39 public:
40 	static Shader[string] m_loadedShaders;
41 
42 	static Shader Find(string glslFile)
43 	{
44 		Shader* existing = glslFile in m_loadedShaders;
45 		if (existing !is null)
46 			return *existing;
47 
48 		Shader newShader = new Shader(glslFile);
49 		m_loadedShaders[glslFile] = newShader;
50 		return newShader;
51 	}
52 
53 package:
54 	this(string glslFile)
55 	{
56 		import meld.fileWatcher : FileWatcher;
57 		FileWatcher.Watch(glslFile, ()
58 		{
59 			writeln("Reloading " ~ glslFile);
60 			Destroy();
61 			Load(glslFile);
62 		});
63 		Load(glslFile);
64 	}
65 
66 	~this()
67 	{
68 		Destroy();
69 	}
70 
71 private:
72 	void Destroy()
73 	{
74 		glDeleteShader(m_vertexShader);
75 		glDeleteShader(m_pixelShader);
76 		glDeleteProgram(m_program);
77 	}
78 
79 	void LoadAndCompile( string shaderSource, GLuint shader )
80 	{
81 		//Attach source and compile
82 		int len = cast(int)shaderSource.length;
83 		const char* source = shaderSource.toStringz();
84 		glShaderSource(shader, 1, cast(const GLchar**)&source, &len);
85 		glCompileShader(shader);
86 
87 		//Did we compile ok?
88 		GLint compiled;
89 		glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
90 		if (!compiled)
91 			throw new Exception("Shader compilation failed!" ~ newline ~ GetLog(&glGetShaderInfoLog, shader));
92 		
93 		glAttachShader(m_program, shader);
94 		glBindAttribLocation(m_program, 0, "inVertex");
95 		glBindAttribLocation(m_program, 1, "inNormal");
96 		glBindAttribLocation(m_program, 2, "inTex");
97 	}
98 
99 	static string GetLog(T)(T method, GLint object)
100 	{
101 		GLint logLength;
102 		GLsizei actualLogLength = 0;
103 		glGetShaderiv(object, GL_INFO_LOG_LENGTH, &logLength);
104 		if (logLength <= 0)
105 			return "";
106 
107 		GLchar[] compilerLog = new GLchar[logLength];
108 		(*method)(object, logLength, &actualLogLength, cast(char*)compilerLog);
109 
110 		return to!string(compilerLog);
111 	}
112 
113 	void Link()
114 	{
115 		glLinkProgram(m_program);
116 
117 		//Did we link ok?
118 		GLint linked;
119 		glGetProgramiv(m_program, GL_LINK_STATUS, &linked);
120 		if (!linked)
121 			throw new Exception("Failed to link shader: " ~ GetLog(&glGetProgramInfoLog, m_program));
122 	}
123 
124 	void Load(string glslFile)
125 	{
126 		auto file = File(glslFile);
127 
128 		ShaderType mode = ShaderType.None;
129 		char[] vertexContents, pixelContents;
130 
131 		foreach (char[] line; file.byLine())
132 		{
133 			if (line.startsWith("#pragma vertex"))
134 				mode = ShaderType.Vertex;
135 			else if (line.startsWith("#pragma fragment"))
136 				mode = ShaderType.Pixel;
137 			else if (mode == ShaderType.Vertex)
138 				vertexContents ~= line ~ newline;
139 			else if (mode == ShaderType.Pixel)
140 				pixelContents ~= line ~ newline;
141 		}
142 
143  		m_program = glCreateProgram();
144 
145 		LoadAndCompile(cast(string)vertexContents, glCreateShader(GL_VERTEX_SHADER));
146 		LoadAndCompile(cast(string)pixelContents, glCreateShader(GL_FRAGMENT_SHADER));
147 		Link();
148 		Bind();
149 
150 		//Reset textures
151 		foreach (string paramName; m_textures.byKey)
152 		{
153 			GLint loc = glGetUniformLocation(m_program, paramName.toStringz);
154 			GLint tu = m_textures[paramName].textureUnit;
155 			glUniform1i(loc, tu);
156 		}
157 	}
158 
159 package:
160 	bool Bind()
161 	{
162 		if (s_currentShader != m_program)
163 		{
164 			s_currentShader = m_program;
165 			glUseProgram(m_program);
166 			return true;
167 		}
168 		return false;
169 	}
170 
171 	void SetParameter( string paramName, mat4 matrix )
172 	{
173 		glUniformMatrix4fv(glGetUniformLocation(m_program, paramName.toStringz), 1, GL_TRUE, cast(float*)&matrix);
174 	}
175 
176 	void SetParameter( string paramName, vec4 vector )
177 	{
178 		glUniform4fv(glGetUniformLocation(m_program, paramName.toStringz), 1, cast(float*)&vector);
179 	}
180 
181 	void SetParameter( string paramName, vec3 vector )
182 	{
183 		glUniform3fv(glGetUniformLocation(m_program, paramName.toStringz), 1, cast(float*)&vector);
184 	}
185 
186 	void SetParameter( string paramName, vec2 vector )
187 	{
188 		glUniform2fv(glGetUniformLocation(m_program, paramName.toStringz), 1, cast(float*)&vector);
189 	}
190 
191 	void SetParameter( string paramName, Texture texture )
192 	{
193 		if (texture.m_texture != 0)
194 		{
195 			TextureUnit* textureParam = paramName in m_textures;
196 
197 			if (textureParam == null)
198 			{
199 				m_textures[paramName] = TextureUnit(cast(GLint)m_textures.length * 2, texture);
200 				textureParam = &m_textures[paramName];
201 
202 				GLint loc = glGetUniformLocation(m_program, paramName.toStringz);
203 				glUniform1i(loc, textureParam.textureUnit);
204 			}
205 			else
206 				textureParam.texture = texture;
207 
208 			glActiveTexture(GL_TEXTURE0 + textureParam.textureUnit);
209 			glBindTexture(GL_TEXTURE_2D, textureParam.texture.m_texture);
210 		}
211 	}
212 
213 	void SetParameter( string paramName, GLuint value )
214 	{
215 		glUniform1i(glGetUniformLocation(m_program, paramName.toStringz), value);
216 	}
217 
218 	void SetParameter( string paramName, float value )
219 	{
220 		glUniform1f(glGetUniformLocation(m_program, paramName.toStringz), value);
221 	}
222 }
223