/*******************************************************************************
* Copyright 2011 See AUTHORS file.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.badlogic.gdx.graphics.g3d.shaders;
import com.badlogic.gdx.graphics.Camera;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GLTexture;
import com.badlogic.gdx.graphics.Mesh;
import com.badlogic.gdx.graphics.VertexAttribute;
import com.badlogic.gdx.graphics.VertexAttributes;
import com.badlogic.gdx.graphics.g3d.Attributes;
import com.badlogic.gdx.graphics.g3d.Renderable;
import com.badlogic.gdx.graphics.g3d.Shader;
import com.badlogic.gdx.graphics.g3d.utils.RenderContext;
import com.badlogic.gdx.graphics.g3d.utils.TextureDescriptor;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import com.badlogic.gdx.math.Matrix3;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.GdxRuntimeException;
import com.badlogic.gdx.utils.IntArray;
import com.badlogic.gdx.utils.IntIntMap;
/** @author Xoppa A BaseShader is a wrapper around a ShaderProgram that keeps track of the uniform and attribute locations. It does
* not manage the ShaderPogram, you are still responsible for disposing the ShaderProgram. */
public abstract class BaseShader implements Shader {
public interface Validator {
/** @return True if the input is valid for the renderable, false otherwise. */
boolean validate (final BaseShader shader, final int inputID, final Renderable renderable);
}
public interface Setter {
/** @return True if the uniform only has to be set once per render call, false if the uniform must be set for each renderable. */
boolean isGlobal (final BaseShader shader, final int inputID);
void set (final BaseShader shader, final int inputID, final Renderable renderable, final Attributes combinedAttributes);
}
public abstract static class GlobalSetter implements Setter {
@Override
public boolean isGlobal (final BaseShader shader, final int inputID) {
return true;
}
}
public abstract static class LocalSetter implements Setter {
@Override
public boolean isGlobal (final BaseShader shader, final int inputID) {
return false;
}
}
public static class Uniform implements Validator {
public final String alias;
public final long materialMask;
public final long environmentMask;
public final long overallMask;
public Uniform (final String alias, final long materialMask, final long environmentMask, final long overallMask) {
this.alias = alias;
this.materialMask = materialMask;
this.environmentMask = environmentMask;
this.overallMask = overallMask;
}
public Uniform (final String alias, final long materialMask, final long environmentMask) {
this(alias, materialMask, environmentMask, 0);
}
public Uniform (final String alias, final long overallMask) {
this(alias, 0, 0, overallMask);
}
public Uniform (final String alias) {
this(alias, 0, 0);
}
public boolean validate (final BaseShader shader, final int inputID, final Renderable renderable) {
final long matFlags = (renderable != null && renderable.material != null) ? renderable.material.getMask() : 0;
final long envFlags = (renderable != null && renderable.environment != null) ? renderable.environment.getMask() : 0;
return ((matFlags & materialMask) == materialMask) && ((envFlags & environmentMask) == environmentMask)
&& (((matFlags | envFlags) & overallMask) == overallMask);
}
}
private final Array<String> uniforms = new Array<String>();
private final Array<Validator> validators = new Array<Validator>();
private final Array<Setter> setters = new Array<Setter>();
private int locations[];
private final IntArray globalUniforms = new IntArray();
private final IntArray localUniforms = new IntArray();
private final IntIntMap attributes = new IntIntMap();
public ShaderProgram program;
public RenderContext context;
public Camera camera;
private Mesh currentMesh;
/** Register an uniform which might be used by this shader. Only possible prior to the call to init().
* @return The ID of the uniform to use in this shader. */
public int register (final String alias, final Validator validator, final Setter setter) {
if (locations != null) throw new GdxRuntimeException("Cannot register an uniform after initialization");
final int existing = getUniformID(alias);
if (existing >= 0) {
validators.set(existing, validator);
setters.set(existing, setter);
return existing;
}
uniforms.add(alias);
validators.add(validator);
setters.add(setter);
return uniforms.size - 1;
}
public int register (final String alias, final Validator validator) {
return register(alias, validator, null);
}
public int register (final String alias, final Setter setter) {
return register(alias, null, setter);
}
public int register (final String alias) {
return register(alias, null, null);
}
public int register (final Uniform uniform, final Setter setter) {
return register(uniform.alias, uniform, setter);
}
public int register (final Uniform uniform) {
return register(uniform, null);
}
/** @return the ID of the input or negative if not available. */
public int getUniformID (final String alias) {
final int n = uniforms.size;
for (int i = 0; i < n; i++)
if (uniforms.get(i).equals(alias)) return i;
return -1;
}
/** @return The input at the specified id. */
public String getUniformAlias (final int id) {
return uniforms.get(id);
}
/** Initialize this shader, causing all registered uniforms/attributes to be fetched. */
public void init (final ShaderProgram program, final Renderable renderable) {
if (locations != null) throw new GdxRuntimeException("Already initialized");
if (!program.isCompiled()) throw new GdxRuntimeException(program.getLog());
this.program = program;
final int n = uniforms.size;
locations = new int[n];
for (int i = 0; i < n; i++) {
final String input = uniforms.get(i);
final Validator validator = validators.get(i);
final Setter setter = setters.get(i);
if (validator != null && !validator.validate(this, i, renderable))
locations[i] = -1;
else {
locations[i] = program.fetchUniformLocation(input, false);
if (locations[i] >= 0 && setter != null) {
if (setter.isGlobal(this, i))
globalUniforms.add(i);
else
localUniforms.add(i);
}
}
if (locations[i] < 0) {
validators.set(i, null);
setters.set(i, null);
}
}
if (renderable != null) {
final VertexAttributes attrs = renderable.meshPart.mesh.getVertexAttributes();
final int c = attrs.size();
for (int i = 0; i < c; i++) {
final VertexAttribute attr = attrs.get(i);
final int location = program.getAttributeLocation(attr.alias);
if (location >= 0) attributes.put(attr.getKey(), location);
}
}
}
@Override
public void begin (Camera camera, RenderContext context) {
this.camera = camera;
this.context = context;
program.begin();
currentMesh = null;
for (int u, i = 0; i < globalUniforms.size; ++i)
if (setters.get(u = globalUniforms.get(i)) != null) setters.get(u).set(this, u, null, null);
}
private final IntArray tempArray = new IntArray();
private final int[] getAttributeLocations (final VertexAttributes attrs) {
tempArray.clear();
final int n = attrs.size();
for (int i = 0; i < n; i++) {
tempArray.add(attributes.get(attrs.get(i).getKey(), -1));
}
tempArray.shrink();
return tempArray.items;
}
private Attributes combinedAttributes = new Attributes();
@Override
public void render (Renderable renderable) {
if (renderable.worldTransform.det3x3() == 0) return;
combinedAttributes.clear();
if (renderable.environment != null) combinedAttributes.set(renderable.environment);
if (renderable.material != null) combinedAttributes.set(renderable.material);
render(renderable, combinedAttributes);
}
public void render (Renderable renderable, final Attributes combinedAttributes) {
for (int u, i = 0; i < localUniforms.size; ++i)
if (setters.get(u = localUniforms.get(i)) != null) setters.get(u).set(this, u, renderable, combinedAttributes);
if (currentMesh != renderable.meshPart.mesh) {
if (currentMesh != null) currentMesh.unbind(program, tempArray.items);
currentMesh = renderable.meshPart.mesh;
currentMesh.bind(program, getAttributeLocations(renderable.meshPart.mesh.getVertexAttributes()));
}
renderable.meshPart.render(program, false);
}
@Override
public void end () {
if (currentMesh != null) {
currentMesh.unbind(program, tempArray.items);
currentMesh = null;
}
program.end();
}
@Override
public void dispose () {
program = null;
uniforms.clear();
validators.clear();
setters.clear();
localUniforms.clear();
globalUniforms.clear();
locations = null;
}
/** Whether this Shader instance implements the specified uniform, only valid after a call to init(). */
public final boolean has (final int inputID) {
return inputID >= 0 && inputID < locations.length && locations[inputID] >= 0;
}
public final int loc (final int inputID) {
return (inputID >= 0 && inputID < locations.length) ? locations[inputID] : -1;
}
public final boolean set (final int uniform, final Matrix4 value) {
if (locations[uniform] < 0) return false;
program.setUniformMatrix(locations[uniform], value);
return true;
}
public final boolean set (final int uniform, final Matrix3 value) {
if (locations[uniform] < 0) return false;
program.setUniformMatrix(locations[uniform], value);
return true;
}
public final boolean set (final int uniform, final Vector3 value) {
if (locations[uniform] < 0) return false;
program.setUniformf(locations[uniform], value);
return true;
}
public final boolean set (final int uniform, final Vector2 value) {
if (locations[uniform] < 0) return false;
program.setUniformf(locations[uniform], value);
return true;
}
public final boolean set (final int uniform, final Color value) {
if (locations[uniform] < 0) return false;
program.setUniformf(locations[uniform], value);
return true;
}
public final boolean set (final int uniform, final float value) {
if (locations[uniform] < 0) return false;
program.setUniformf(locations[uniform], value);
return true;
}
public final boolean set (final int uniform, final float v1, final float v2) {
if (locations[uniform] < 0) return false;
program.setUniformf(locations[uniform], v1, v2);
return true;
}
public final boolean set (final int uniform, final float v1, final float v2, final float v3) {
if (locations[uniform] < 0) return false;
program.setUniformf(locations[uniform], v1, v2, v3);
return true;
}
public final boolean set (final int uniform, final float v1, final float v2, final float v3, final float v4) {
if (locations[uniform] < 0) return false;
program.setUniformf(locations[uniform], v1, v2, v3, v4);
return true;
}
public final boolean set (final int uniform, final int value) {
if (locations[uniform] < 0) return false;
program.setUniformi(locations[uniform], value);
return true;
}
public final boolean set (final int uniform, final int v1, final int v2) {
if (locations[uniform] < 0) return false;
program.setUniformi(locations[uniform], v1, v2);
return true;
}
public final boolean set (final int uniform, final int v1, final int v2, final int v3) {
if (locations[uniform] < 0) return false;
program.setUniformi(locations[uniform], v1, v2, v3);
return true;
}
public final boolean set (final int uniform, final int v1, final int v2, final int v3, final int v4) {
if (locations[uniform] < 0) return false;
program.setUniformi(locations[uniform], v1, v2, v3, v4);
return true;
}
public final boolean set (final int uniform, final TextureDescriptor textureDesc) {
if (locations[uniform] < 0) return false;
program.setUniformi(locations[uniform], context.textureBinder.bind(textureDesc));
return true;
}
public final boolean set (final int uniform, final GLTexture texture) {
if (locations[uniform] < 0) return false;
program.setUniformi(locations[uniform], context.textureBinder.bind(texture));
return true;
}
}