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