package org.saintandreas.gl.shaders;
import static org.lwjgl.opengl.GL11.GL_TRUE;
import static org.lwjgl.opengl.GL20.GL_ACTIVE_ATTRIBUTES;
import static org.lwjgl.opengl.GL20.GL_ACTIVE_UNIFORMS;
import static org.lwjgl.opengl.GL20.GL_FRAGMENT_SHADER;
import static org.lwjgl.opengl.GL20.GL_LINK_STATUS;
import static org.lwjgl.opengl.GL20.GL_VERTEX_SHADER;
import static org.lwjgl.opengl.GL20.glCreateProgram;
import static org.lwjgl.opengl.GL20.glDeleteProgram;
import static org.lwjgl.opengl.GL20.glGetActiveAttrib;
import static org.lwjgl.opengl.GL20.glGetActiveUniform;
import static org.lwjgl.opengl.GL20.glGetAttribLocation;
import static org.lwjgl.opengl.GL20.glGetProgramInfoLog;
import static org.lwjgl.opengl.GL20.glGetProgrami;
import static org.lwjgl.opengl.GL20.glGetUniformLocation;
import static org.lwjgl.opengl.GL20.glLinkProgram;
import static org.lwjgl.opengl.GL20.glUniform1f;
import static org.lwjgl.opengl.GL20.glUniform1i;
import static org.lwjgl.opengl.GL20.glUniform3f;
import static org.lwjgl.opengl.GL20.glUniform4f;
import static org.lwjgl.opengl.GL20.glUniformMatrix4;
import static org.lwjgl.opengl.GL20.glUseProgram;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.HashMap;
import java.util.Map;
import org.saintandreas.gl.BufferUtils;
import org.saintandreas.gl.OpenGL;
import org.saintandreas.math.Matrix4f;
import org.saintandreas.math.Vector3f;
import org.saintandreas.math.Vector4f;
import org.saintandreas.resources.Resource;
import org.saintandreas.resources.ResourceManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Program {
private static final Logger LOG = LoggerFactory.getLogger(Program.class);
enum ShaderType {
VERTEX, GEOMETRY, FRAGMENT
};
private final int VERTEX_SHADER = 0;
private final int GEOMETRY_SHADER = 1;
private final int FRAGMENT_SHADER = 2;
public final Map<String, Integer> uniforms = new HashMap<String, Integer>();
public final Map<String, Integer> attributes = new HashMap<String, Integer>();
private static Program CURRENT_PROGRAM = null;
private Shader[] shaders = new Shader[3];
public int program = -1;
public Program(Resource vs, Resource fs) {
this(ResourceManager.getProvider().getAsString(vs), ResourceManager.getProvider().getAsString(fs));
}
public Program(String vssf, String fssf) {
this(new Shader(GL_VERTEX_SHADER, vssf), null, new Shader(GL_FRAGMENT_SHADER, fssf));
}
public Program(Shader vs, Shader fs) {
this(vs, null, fs);
}
public Program(Shader vs, Shader gs, Shader fs) {
shaders[VERTEX_SHADER] = vs;
shaders[GEOMETRY_SHADER] = gs;
shaders[FRAGMENT_SHADER] = fs;
}
public void destroy() {
if (program != -1) {
glDeleteProgram(program);
program = -1;
}
}
public void link() {
for (Shader s : shaders) {
if (null != s && s.shader == -1) {
s.compile();
}
}
int newProgram = linkProgram(shaders);
if (program != -1) {
glDeleteProgram(program);
program = -1;
}
this.uniforms.clear();
this.attributes.clear();
program = newProgram;
if (program == -1) {
throw new IllegalStateException("Link failure");
}
int count = glGetProgrami(program, GL_ACTIVE_UNIFORMS);
for (int i = 0; i < count; ++i) {
String name = glGetActiveUniform(program, i, 256);
int location = glGetUniformLocation(program, name);
this.uniforms.put(name, location);
}
count = glGetProgrami(program, GL_ACTIVE_ATTRIBUTES);
for (int i = 0; i < count; ++i) {
String name = glGetActiveAttrib(program, i, 256);
int location = glGetAttribLocation(program, name);
this.attributes.put(name, location);
}
}
public String getLog() {
return getLog(program);
}
public void use() {
if (program == -1) {
throw new IllegalStateException("Program is not linked");
}
CURRENT_PROGRAM = this;
glUseProgram(program);
OpenGL.checkError();
}
public static void clear() {
glUseProgram(0);
CURRENT_PROGRAM = null;
}
protected int getUniformLocation(String string) {
if (!uniforms.containsKey(string)) {
return -1;
}
return this.uniforms.get(string);
}
private void checkCurrent() {
if (this != CURRENT_PROGRAM) {
throw new IllegalStateException("Attempting to set uniform on unbound program");
}
}
public void setUniform(final String string, int value) {
checkCurrent();
int location = getUniformLocation(string);
glUniform1i(location, value);
}
public void setUniform(final String string, float value) {
checkCurrent();
int location = getUniformLocation(string);
glUniform1f(location, value);
}
public void setUniform(final String string, Vector4f value) {
checkCurrent();
int location = getUniformLocation(string);
glUniform4f(location, value.x, value.y, value.z, value.w);
}
public void setUniform(final String string, Vector3f value) {
checkCurrent();
int location = getUniformLocation(string);
glUniform3f(location, value.x, value.y, value.z);
}
public void setUniformMatrix4(final String name, FloatBuffer fb) {
checkCurrent();
int location = getUniformLocation(name);
glUniformMatrix4(location, false, fb);
}
public void setUniformMatrix4(final String name, float[] v) {
FloatBuffer fb = ByteBuffer.allocateDirect(v.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
fb.put(v);
fb.position(0);
setUniformMatrix4(name, fb);
}
public void setUniform(final String name, Matrix4f m) {
FloatBuffer fb = BufferUtils.getFloatBuffer(16);
m.fillFloatBuffer(fb, true);
fb.position(0);
setUniformMatrix4(name, fb);
}
public static String getLog(int program) {
return glGetProgramInfoLog(program, 8192);
}
public static int linkProgram(Shader... shaders) {
int newProgram = glCreateProgram();
for (Shader s : shaders) {
if (null != s) {
s.attach(newProgram);
}
}
glLinkProgram(newProgram);
int linkResult = glGetProgrami(newProgram, GL_LINK_STATUS);
if (GL_TRUE != linkResult) {
String log = getLog(newProgram);
LOG.warn("Link failed: " + log);
glDeleteProgram(newProgram);
throw new RuntimeException(log);
}
return newProgram;
}
}