1/*******************************************************************************
2 * Copyright 2011 See AUTHORS file.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *   http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 ******************************************************************************/
16
17package com.badlogic.gdx.graphics.g3d.shaders;
18
19import com.badlogic.gdx.graphics.Camera;
20import com.badlogic.gdx.graphics.Color;
21import com.badlogic.gdx.graphics.GLTexture;
22import com.badlogic.gdx.graphics.Mesh;
23import com.badlogic.gdx.graphics.VertexAttribute;
24import com.badlogic.gdx.graphics.VertexAttributes;
25import com.badlogic.gdx.graphics.g3d.Attributes;
26import com.badlogic.gdx.graphics.g3d.Renderable;
27import com.badlogic.gdx.graphics.g3d.Shader;
28import com.badlogic.gdx.graphics.g3d.utils.RenderContext;
29import com.badlogic.gdx.graphics.g3d.utils.TextureDescriptor;
30import com.badlogic.gdx.graphics.glutils.ShaderProgram;
31import com.badlogic.gdx.math.Matrix3;
32import com.badlogic.gdx.math.Matrix4;
33import com.badlogic.gdx.math.Vector2;
34import com.badlogic.gdx.math.Vector3;
35import com.badlogic.gdx.utils.Array;
36import com.badlogic.gdx.utils.GdxRuntimeException;
37import com.badlogic.gdx.utils.IntArray;
38import com.badlogic.gdx.utils.IntIntMap;
39
40/** @author Xoppa A BaseShader is a wrapper around a ShaderProgram that keeps track of the uniform and attribute locations. It does
41 *         not manage the ShaderPogram, you are still responsible for disposing the ShaderProgram. */
42public abstract class BaseShader implements Shader {
43	public interface Validator {
44		/** @return True if the input is valid for the renderable, false otherwise. */
45		boolean validate (final BaseShader shader, final int inputID, final Renderable renderable);
46	}
47
48	public interface Setter {
49		/** @return True if the uniform only has to be set once per render call, false if the uniform must be set for each renderable. */
50		boolean isGlobal (final BaseShader shader, final int inputID);
51
52		void set (final BaseShader shader, final int inputID, final Renderable renderable, final Attributes combinedAttributes);
53	}
54
55	public abstract static class GlobalSetter implements Setter {
56		@Override
57		public boolean isGlobal (final BaseShader shader, final int inputID) {
58			return true;
59		}
60	}
61
62	public abstract static class LocalSetter implements Setter {
63		@Override
64		public boolean isGlobal (final BaseShader shader, final int inputID) {
65			return false;
66		}
67	}
68
69	public static class Uniform implements Validator {
70		public final String alias;
71		public final long materialMask;
72		public final long environmentMask;
73		public final long overallMask;
74
75		public Uniform (final String alias, final long materialMask, final long environmentMask, final long overallMask) {
76			this.alias = alias;
77			this.materialMask = materialMask;
78			this.environmentMask = environmentMask;
79			this.overallMask = overallMask;
80		}
81
82		public Uniform (final String alias, final long materialMask, final long environmentMask) {
83			this(alias, materialMask, environmentMask, 0);
84		}
85
86		public Uniform (final String alias, final long overallMask) {
87			this(alias, 0, 0, overallMask);
88		}
89
90		public Uniform (final String alias) {
91			this(alias, 0, 0);
92		}
93
94		public boolean validate (final BaseShader shader, final int inputID, final Renderable renderable) {
95			final long matFlags = (renderable != null && renderable.material != null) ? renderable.material.getMask() : 0;
96			final long envFlags = (renderable != null && renderable.environment != null) ? renderable.environment.getMask() : 0;
97			return ((matFlags & materialMask) == materialMask) && ((envFlags & environmentMask) == environmentMask)
98				&& (((matFlags | envFlags) & overallMask) == overallMask);
99		}
100	}
101
102	private final Array<String> uniforms = new Array<String>();
103	private final Array<Validator> validators = new Array<Validator>();
104	private final Array<Setter> setters = new Array<Setter>();
105	private int locations[];
106	private final IntArray globalUniforms = new IntArray();
107	private final IntArray localUniforms = new IntArray();
108	private final IntIntMap attributes = new IntIntMap();
109
110	public ShaderProgram program;
111	public RenderContext context;
112	public Camera camera;
113	private Mesh currentMesh;
114
115	/** Register an uniform which might be used by this shader. Only possible prior to the call to init().
116	 * @return The ID of the uniform to use in this shader. */
117	public int register (final String alias, final Validator validator, final Setter setter) {
118		if (locations != null) throw new GdxRuntimeException("Cannot register an uniform after initialization");
119		final int existing = getUniformID(alias);
120		if (existing >= 0) {
121			validators.set(existing, validator);
122			setters.set(existing, setter);
123			return existing;
124		}
125		uniforms.add(alias);
126		validators.add(validator);
127		setters.add(setter);
128		return uniforms.size - 1;
129	}
130
131	public int register (final String alias, final Validator validator) {
132		return register(alias, validator, null);
133	}
134
135	public int register (final String alias, final Setter setter) {
136		return register(alias, null, setter);
137	}
138
139	public int register (final String alias) {
140		return register(alias, null, null);
141	}
142
143	public int register (final Uniform uniform, final Setter setter) {
144		return register(uniform.alias, uniform, setter);
145	}
146
147	public int register (final Uniform uniform) {
148		return register(uniform, null);
149	}
150
151	/** @return the ID of the input or negative if not available. */
152	public int getUniformID (final String alias) {
153		final int n = uniforms.size;
154		for (int i = 0; i < n; i++)
155			if (uniforms.get(i).equals(alias)) return i;
156		return -1;
157	}
158
159	/** @return The input at the specified id. */
160	public String getUniformAlias (final int id) {
161		return uniforms.get(id);
162	}
163
164	/** Initialize this shader, causing all registered uniforms/attributes to be fetched. */
165	public void init (final ShaderProgram program, final Renderable renderable) {
166		if (locations != null) throw new GdxRuntimeException("Already initialized");
167		if (!program.isCompiled()) throw new GdxRuntimeException(program.getLog());
168		this.program = program;
169
170		final int n = uniforms.size;
171		locations = new int[n];
172		for (int i = 0; i < n; i++) {
173			final String input = uniforms.get(i);
174			final Validator validator = validators.get(i);
175			final Setter setter = setters.get(i);
176			if (validator != null && !validator.validate(this, i, renderable))
177				locations[i] = -1;
178			else {
179				locations[i] = program.fetchUniformLocation(input, false);
180				if (locations[i] >= 0 && setter != null) {
181					if (setter.isGlobal(this, i))
182						globalUniforms.add(i);
183					else
184						localUniforms.add(i);
185				}
186			}
187			if (locations[i] < 0) {
188				validators.set(i, null);
189				setters.set(i, null);
190			}
191		}
192		if (renderable != null) {
193			final VertexAttributes attrs = renderable.meshPart.mesh.getVertexAttributes();
194			final int c = attrs.size();
195			for (int i = 0; i < c; i++) {
196				final VertexAttribute attr = attrs.get(i);
197				final int location = program.getAttributeLocation(attr.alias);
198				if (location >= 0) attributes.put(attr.getKey(), location);
199			}
200		}
201	}
202
203	@Override
204	public void begin (Camera camera, RenderContext context) {
205		this.camera = camera;
206		this.context = context;
207		program.begin();
208		currentMesh = null;
209		for (int u, i = 0; i < globalUniforms.size; ++i)
210			if (setters.get(u = globalUniforms.get(i)) != null) setters.get(u).set(this, u, null, null);
211	}
212
213	private final IntArray tempArray = new IntArray();
214
215	private final int[] getAttributeLocations (final VertexAttributes attrs) {
216		tempArray.clear();
217		final int n = attrs.size();
218		for (int i = 0; i < n; i++) {
219			tempArray.add(attributes.get(attrs.get(i).getKey(), -1));
220		}
221		return tempArray.items;
222	}
223
224	private Attributes combinedAttributes = new Attributes();
225
226	@Override
227	public void render (Renderable renderable) {
228		if (renderable.worldTransform.det3x3() == 0) return;
229		combinedAttributes.clear();
230		if (renderable.environment != null) combinedAttributes.set(renderable.environment);
231		if (renderable.material != null) combinedAttributes.set(renderable.material);
232		render(renderable, combinedAttributes);
233	}
234
235	public void render (Renderable renderable, final Attributes combinedAttributes) {
236		for (int u, i = 0; i < localUniforms.size; ++i)
237			if (setters.get(u = localUniforms.get(i)) != null) setters.get(u).set(this, u, renderable, combinedAttributes);
238		if (currentMesh != renderable.meshPart.mesh) {
239			if (currentMesh != null) currentMesh.unbind(program, tempArray.items);
240			currentMesh = renderable.meshPart.mesh;
241			currentMesh.bind(program, getAttributeLocations(renderable.meshPart.mesh.getVertexAttributes()));
242		}
243		renderable.meshPart.render(program, false);
244	}
245
246	@Override
247	public void end () {
248		if (currentMesh != null) {
249			currentMesh.unbind(program, tempArray.items);
250			currentMesh = null;
251		}
252		program.end();
253	}
254
255	@Override
256	public void dispose () {
257		program = null;
258		uniforms.clear();
259		validators.clear();
260		setters.clear();
261		localUniforms.clear();
262		globalUniforms.clear();
263		locations = null;
264	}
265
266	/** Whether this Shader instance implements the specified uniform, only valid after a call to init(). */
267	public final boolean has (final int inputID) {
268		return inputID >= 0 && inputID < locations.length && locations[inputID] >= 0;
269	}
270
271	public final int loc (final int inputID) {
272		return (inputID >= 0 && inputID < locations.length) ? locations[inputID] : -1;
273	}
274
275	public final boolean set (final int uniform, final Matrix4 value) {
276		if (locations[uniform] < 0) return false;
277		program.setUniformMatrix(locations[uniform], value);
278		return true;
279	}
280
281	public final boolean set (final int uniform, final Matrix3 value) {
282		if (locations[uniform] < 0) return false;
283		program.setUniformMatrix(locations[uniform], value);
284		return true;
285	}
286
287	public final boolean set (final int uniform, final Vector3 value) {
288		if (locations[uniform] < 0) return false;
289		program.setUniformf(locations[uniform], value);
290		return true;
291	}
292
293	public final boolean set (final int uniform, final Vector2 value) {
294		if (locations[uniform] < 0) return false;
295		program.setUniformf(locations[uniform], value);
296		return true;
297	}
298
299	public final boolean set (final int uniform, final Color value) {
300		if (locations[uniform] < 0) return false;
301		program.setUniformf(locations[uniform], value);
302		return true;
303	}
304
305	public final boolean set (final int uniform, final float value) {
306		if (locations[uniform] < 0) return false;
307		program.setUniformf(locations[uniform], value);
308		return true;
309	}
310
311	public final boolean set (final int uniform, final float v1, final float v2) {
312		if (locations[uniform] < 0) return false;
313		program.setUniformf(locations[uniform], v1, v2);
314		return true;
315	}
316
317	public final boolean set (final int uniform, final float v1, final float v2, final float v3) {
318		if (locations[uniform] < 0) return false;
319		program.setUniformf(locations[uniform], v1, v2, v3);
320		return true;
321	}
322
323	public final boolean set (final int uniform, final float v1, final float v2, final float v3, final float v4) {
324		if (locations[uniform] < 0) return false;
325		program.setUniformf(locations[uniform], v1, v2, v3, v4);
326		return true;
327	}
328
329	public final boolean set (final int uniform, final int value) {
330		if (locations[uniform] < 0) return false;
331		program.setUniformi(locations[uniform], value);
332		return true;
333	}
334
335	public final boolean set (final int uniform, final int v1, final int v2) {
336		if (locations[uniform] < 0) return false;
337		program.setUniformi(locations[uniform], v1, v2);
338		return true;
339	}
340
341	public final boolean set (final int uniform, final int v1, final int v2, final int v3) {
342		if (locations[uniform] < 0) return false;
343		program.setUniformi(locations[uniform], v1, v2, v3);
344		return true;
345	}
346
347	public final boolean set (final int uniform, final int v1, final int v2, final int v3, final int v4) {
348		if (locations[uniform] < 0) return false;
349		program.setUniformi(locations[uniform], v1, v2, v3, v4);
350		return true;
351	}
352
353	public final boolean set (final int uniform, final TextureDescriptor textureDesc) {
354		if (locations[uniform] < 0) return false;
355		program.setUniformi(locations[uniform], context.textureBinder.bind(textureDesc));
356		return true;
357	}
358
359	public final boolean set (final int uniform, final GLTexture texture) {
360		if (locations[uniform] < 0) return false;
361		program.setUniformi(locations[uniform], context.textureBinder.bind(texture));
362		return true;
363	}
364}
365