package ch.ethz.karto.map3d;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import javax.media.opengl.GL;
import javax.media.opengl.GL2;
import javax.media.opengl.GLException;
/**
* Vertex and fragment shader.
* @author Bernhard Jenny, Institute of Cartography, ETH ZUrich
*/
public class Map3DShader {
private static final String INVALID_UNIFORM_EXCEPTION_MSG = "Invalid uniform "
+ "location. Uniform may be defined but not be used by the shader code.";
private int vertexShader, fragmentShader, shaderProgram;
protected Map3DShader() {
}
/**
* Loads a vertex and a fragment shader from two files; compiles and links
* the shaders; and creates and links a shader program and attaches the
* shaders. At the end the shaders are ready to be used.
* @param gl
* @param vShaderPath A path to a file with the vertex shader code.
* @param fShaderPath A path to a file with the fragment shader code.
* @param attribName Array of attribute names used in shaders. Can be null.
* @param attribID Array of indices associated with the attribute names.
* Can be null.
*/
protected void enableShaders(GL gl1, String vShaderPath, String fShaderPath,
String[] attribName, int[] attribID) {
GL2 gl = (GL2)gl1;
int[] params = new int[1];
String src = loadShader(vShaderPath);
vertexShader = enableShader(gl, src, GL2.GL_VERTEX_SHADER);
src = loadShader(fShaderPath);
fragmentShader = enableShader(gl, src, GL2.GL_FRAGMENT_SHADER);
shaderProgram = gl.glCreateProgram();
gl.glAttachShader(shaderProgram, vertexShader);
gl.glAttachShader(shaderProgram, fragmentShader);
// bind attribute names used in shaders to predefined locations
// slot 0 should be used, normally for vertices
// http://www.gamedev.net/community/forums/topic.asp?topic_id=413008
if (attribName != null && attribID != null) {
assert attribName.length == attribID.length;
for (int i = 0; i < attribID.length; i++) {
gl.glBindAttribLocation(shaderProgram, attribID[i], attribName[i]);
}
}
gl.glLinkProgram(shaderProgram);
// check for linking error
gl.glGetProgramiv(shaderProgram, GL2.GL_LINK_STATUS, params, 0);
if (params[0] == 0) {
// failure to link
gl.glGetProgramiv(shaderProgram, GL2.GL_LINK_STATUS, params, 0);
String msg = "Error on shader program linking";
if (params[0] > 0) {
byte[] log = new byte[params[0]];
gl.glGetProgramInfoLog(shaderProgram, params[0], params, 0, log, 0);
msg += ": " + new String(log, 0, log.length - 1);
gl.glDeleteProgram(shaderProgram);
}
throw new GLException(msg);
}
gl.glUseProgram(shaderProgram);
}
protected int getAttribLocation(GL gl1, String attribName) {
GL2 gl = (GL2)gl1;
return gl.glGetAttribLocation(shaderProgram, attribName);
}
/**
* Validate a shader program before rendering. This should only be called
* during application development, because it can severly hinder performance.
* @param gl
* @return
*/
protected boolean validateProgram(GL gl1) {
int[] params = new int[1];
GL2 gl = (GL2)gl1;
gl.glValidateProgram(shaderProgram);
gl.glGetProgramiv(shaderProgram, GL2.GL_VALIDATE_STATUS, params, 0);
if (params[0] == 0) {
// validation failed
gl.glGetProgramiv(shaderProgram, GL2.GL_LINK_STATUS, params, 0);
String msg = "Error on shader program validation";
if (params[0] > 0) {
byte[] log = new byte[params[0]];
gl.glGetProgramInfoLog(shaderProgram, params[0], params, 0, log, 0);
msg += ": " + new String(log, 0, log.length - 1);
gl.glDeleteProgram(shaderProgram);
}
System.err.println(msg);
}
return params[0] != 0;
}
protected void disableShaders(GL gl1) {
GL2 gl = (GL2)gl1;
gl.glUseProgram(0);
// glDeleteProgram and glDeleteShader silently ignore a value of 0
gl.glDeleteProgram(shaderProgram);
shaderProgram = 0;
gl.glDeleteShader(vertexShader);
vertexShader = 0;
gl.glDeleteShader(fragmentShader);
fragmentShader = 0;
}
private int enableShader(GL gl1, String src, int shaderType) {
int[] params = new int[1];
GL2 gl = (GL2)gl1;
int shaderID = gl.glCreateShader(shaderType);
if (shaderID != 0 && src != null) {
gl.glShaderSource(shaderID, 1, new String[]{src}, (int[]) null, 0);
gl.glCompileShader(shaderID);
// check for compilation errors
gl.glGetShaderiv(shaderID, GL2.GL_COMPILE_STATUS, params, 0);
if (params[0] == 0) {
int[] buffer = new int[1];
gl.glGetShaderiv(shaderID, GL2.GL_INFO_LOG_LENGTH, buffer, 0);
String msg = "Error on shader compilation";
if (buffer[0] > 0) {
byte[] log = new byte[buffer[0]];
gl.glGetShaderInfoLog(shaderID, buffer[0], buffer, 0, log, 0);
msg += ": " + new String(log, 0, log.length - 1);
gl.glDeleteShader(shaderID);
}
throw new GLException(msg);
}
}
return shaderID;
}
private String loadShader(String shaderPath) {
BufferedReader reader = null;
try {
java.net.URL url = Map3DViewer.class.getResource(shaderPath);
BufferedInputStream bis = new BufferedInputStream(url.openStream());
InputStreamReader isr = new InputStreamReader(bis, "UTF-8");
reader = new BufferedReader(isr);
String line, shader = "";
while ((line = reader.readLine()) != null) {
shader += line + "\n";
}
return shader;
} catch (IOException ex) {
ex.printStackTrace();
return null;
} finally {
try {
if (reader != null) {
reader.close();
}
} catch (Exception ex) {
ex.printStackTrace();
return null;
}
}
}
protected void setUniform(GL gl1, String name, boolean i) {
GL2 gl = (GL2)gl1;
int loc = gl.glGetUniformLocation(shaderProgram, name);
if (loc != -1) {
// a uniform bool in a vertex shader is set using an int
gl.glUniform1i(loc, i ? 1 : 0);
} else {
throw new GLException(INVALID_UNIFORM_EXCEPTION_MSG);
}
}
protected void setUniform(GL gl1, String name, int i) {
GL2 gl = (GL2)gl1;
int loc = gl.glGetUniformLocation(shaderProgram, name);
if (loc != -1) {
gl.glUniform1i(loc, i);
} else {
throw new GLException(INVALID_UNIFORM_EXCEPTION_MSG);
}
}
protected void setUniform(GL gl1, String name, float v) {
GL2 gl = (GL2)gl1;
int loc = gl.glGetUniformLocation(shaderProgram, name);
if (loc != -1) {
gl.glUniform1f(loc, v);
} else {
throw new GLException(INVALID_UNIFORM_EXCEPTION_MSG);
}
}
protected void setUniform(GL gl1, String name, float v1, float v2, float v3) {
GL2 gl = (GL2)gl1;
int loc = gl.glGetUniformLocation(shaderProgram, name);
if (loc != -1) {
gl.glUniform3fv(loc, 1, new float[]{v1, v2, v3}, 0);
} else {
throw new GLException(INVALID_UNIFORM_EXCEPTION_MSG);
}
}
protected void setUniform(GL gl1, String name, float v1, float v2) {
GL2 gl = (GL2)gl1;
int loc = gl.glGetUniformLocation(shaderProgram, name);
if (loc != -1) {
gl.glUniform2fv(loc, 1, new float[]{v1, v2}, 0);
} else {
throw new GLException(INVALID_UNIFORM_EXCEPTION_MSG);
}
}
}