1 module ws.gl.shader;
2 
3 
4 import
5 	file = std.file,
6 	std.string,
7 	ws.string,
8 	ws.math.vector,
9 	ws.math.matrix,
10 	ws.log,
11 	ws.exception,
12 	ws.thread.loader,
13 	ws.gl.gl;
14 
15 
16 __gshared:
17 
18 
19 class Shader: Loadable {
20 	protected:
21 		this(){};
22 		~this(){};
23 		
24 	public:
25 	
26 
27 		static Shader load(string dir, string[uint] attr, string[uint] frag=null){
28 			string id = dir ~ tostring(attr) ~ tostring(frag);
29 			if(id in shaderContainer)
30 				return shaderContainer[id];
31 			auto shader = prepare(id);
32 			shader.start(dir);
33 			shader.bindAttr(attr);
34 			if(frag)
35 				shader.bindFrag(frag);
36 			shader.finish();
37 			return shader;
38 		}
39 
40 		static Shader load(string name, string[uint] attr, string[uint] frag, string vertex, string fragment){
41 			string id = name ~ tostring(attr) ~ tostring(frag);
42 			if(id in shaderContainer)
43 				return shaderContainer[id];
44 			auto shader = prepare(id);
45 			shader.start(name, vertex, fragment);
46 			shader.bindAttr(attr);
47 			if(frag)
48 				shader.bindFrag(frag);
49 			shader.finish();
50 			return shader;
51 		}
52 
53 
54 		static Shader prepare(string id){
55 			if(id in shaderContainer)
56 				return shaderContainer[id];
57 			auto shader = new Shader;
58 			shaderContainer[id] = shader;
59 			return shader;
60 		}
61 		
62 
63 		Uniform opIndex(string name){
64 			if(name in uniforms)
65 				return uniforms[name];
66 			uniforms[name] = new Uniform(name, this);
67 			return uniforms[name];
68 		}
69 		
70 
71 		void applyUniform(Args...)(Args args){
72 			static if(args.length){
73 				opIndex(args[0]).set(args[1]);
74 				//gl.check(args[0]);
75 				applyUniform(args[2..$]);
76 			}
77 		}
78 
79 
80 		void use(Args...)(Args args){
81 			if(!valid || failed)
82 				exception("Trying to use unfinished shader " ~ name);
83 			program.use();
84 			try {
85 				applyUniform(args);
86 			}catch(Exception e){
87 				Log.error("Failed to activate shader: %s".format(e));
88 			}
89 		}
90 
91 
92 		void start(string folder){
93 			start(
94 				folder,
95 				cast(string)file.read("shaders/" ~ folder ~ "/vertex.vp"),
96 				cast(string)file.read("shaders/" ~ folder ~ "/fragment.fp")
97 			);
98 		}
99 
100 
101 		void start(string name, string vertex, string fragment){
102 			assert(!valid);
103 			try {
104 				if(failed) return;
105 				this.name = name;
106 				shaderVertex = new gl.Shader(gl.shaderVertex, vertex);
107 				shaderFragment = new gl.Shader(gl.shaderFragment, fragment);
108 				program = new gl.Program;
109 				program.attach(shaderVertex);
110 				program.attach(shaderFragment);
111 			}catch(Exception e){
112 				exception("Failed to load shader \"" ~ name ~ "\"", e);
113 			}
114 		}
115 
116 
117 		override void finish(){
118 			assert(gl.active());
119 			if(failed)
120 				return;
121 			program.link();
122 			valid = true;
123 			foreach(Uniform u; uniforms)
124 				u.update();
125 		}
126 
127 
128 		void bindAttr(string[uint] attrs){
129 			foreach(i, name; attrs)
130 				glBindAttribLocation(program.program, i, name.toStringz);
131 		}
132 
133 
134 		void bindFrag(string[uint] frags){
135 			foreach(i, name; frags)
136 				glBindFragDataLocation(program.program, i, name.toStringz);
137 		}
138 
139 
140 		gl.Program program;
141 
142 		gl.Shader shaderVertex;
143 		gl.Shader shaderGeometry;
144 		gl.Shader shaderFragment;
145 
146 		bool valid = false;
147 		bool failed = false;
148 
149 		string name;
150 
151 		Uniform[string] uniforms;
152 
153 		static Shader[string] shaderContainer;
154 
155 
156 		static class Uniform {
157 
158 			this(string n, Shader s){
159 				if(!s)
160 					exception("Shader is null");
161 				name = n;
162 				shader = s;
163 				s.uniforms[n] = this;
164 				if(s.valid && !s.failed)
165 					update();
166 				else
167 					valid = false;
168 			};
169 
170 			/*~this(){
171 				shader.uniforms.remove(name);
172 			}*/
173 
174 			void set(T)(T r){
175 				if(valid && shader && shader.valid && !shader.failed){
176 					if(gltype!T() == type)
177 						shader.program.uniform(location, r);
178 					else
179 						throw new Exception("Wrong uniform type for %s: %s (%s), expected %s)"
180 							.format(name, gltype!T(), typeid(T).toString(), type));
181 				}
182 			}
183 
184 			protected:
185 
186 				template Tuple(E...){
187 					alias Tuple = E;
188 				}
189 
190 				alias UniformTypes = Tuple!(
191 					GL_FLOAT, float,
192 					GL_FLOAT_VEC2, float[2],
193 					GL_FLOAT_VEC3, float[3],
194 					GL_FLOAT_VEC4, float[4],
195 					GL_FLOAT_VEC2, Vector!2,
196 					GL_FLOAT_VEC3, Vector!3,
197 					GL_FLOAT_VEC4, Vector!4,
198 					//GL_INT, GLint,
199 					//GL_INT_VEC2, GLint[2],
200 					//GL_INT_VEC3, GLint[3],
201 					//GL_INT_VEC4, GLint[4],
202 					GL_BOOL, GLboolean,
203 					GL_FLOAT_MAT3, Matrix!(3,3),
204 					GL_FLOAT_MAT4, Matrix!(4,4),
205 					//GL_BOOL_VEC2, GLboolean[2],
206 					//GL_BOOL_VEC3, GLboolean[3],
207 					//GL_BOOL_VEC4, GLboolean[4],
208 					GL_SAMPLER_2D, GLint
209 					//GL_FLOAT_MAT2, GL_FLOAT_MAT3, GL_FLOAT_MAT4,
210 					//GL_SAMPLER_2D, GL_SAMPLER_CUBE
211 				);
212 
213 				GLenum gltype(T)(){
214 					foreach(i, u; UniformTypes)
215 						static if(is(u == T))
216 							return UniformTypes[i-1];
217 					assert(false, "%s not an accepted uniform type".format(typeid(T).stringof));
218 				}
219 
220 				int location;
221 				string name;
222 				Shader shader;
223 				GLenum type;
224 				bool valid;
225 
226 				void update(bool dead = false){
227 					if(!dead && shader.valid){
228 						location = shader.program.getUniform(name);
229 						if(location < 0){
230 							dead = true;
231 							Log.warning("Shader \"" ~ shader.name ~ "\": could not find uniform \"" ~ name ~ "\" (optimized out?)");
232 						}else{
233 							char[256] name;
234 							GLsizei length;
235 							GLint size;
236 							glGetActiveUniform(
237 								shader.program.program, location, 256,
238 								&length, &size, &type, name.ptr
239 							);
240 						}
241 					}
242 					valid = !dead;
243 				}
244 		}
245 
246 }
247