package org.archstudio.bna.ui.jogl.utils;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.media.opengl.GL;
import javax.media.opengl.GL2ES2;
import javax.media.opengl.GLException;
import org.archstudio.sysutils.Disposable;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.jogamp.common.nio.Buffers;
public final class GL2ES2Program implements Disposable {
private static int[] vbo;
public static GL2ES2Program create(GL2ES2 gl, GL2ES2Shader... shaders) {
return new GL2ES2Program(gl, shaders);
}
private final GL2ES2 gl;
private final List<GL2ES2Shader> shaders;
private int program;
private final int maxAttributes;
private final Map<String, Integer> attributes = Maps.newHashMap();
private int openAttribute = 1; // 0 is reserved
private boolean valid = false;
private GL2ES2Program(GL2ES2 gl, GL2ES2Shader... shaders) {
this.gl = gl;
this.shaders = Lists.newArrayList(Arrays.asList(shaders));
{
int[] maxAttributes = new int[1];
gl.glGetIntegerv(GL2ES2.GL_MAX_VERTEX_ATTRIBS, maxAttributes, 0);
this.maxAttributes = maxAttributes[0];
}
}
public void bindAttribute(String name, int size) throws GLException {
if (attributes.get(name) != null) {
throw new GLException("Attribute already bound: " + name);
}
if (openAttribute + size >= maxAttributes) {
throw new GLException("Exceeded max attributes.");
}
attributes.put(name, openAttribute);
openAttribute += size;
}
public void link() throws GLException {
dispose();
program = gl.glCreateProgram();
if (program == 0) {
throw new GLException("Cannot create program.");
}
try {
for (GL2ES2Shader shader : shaders) {
gl.glAttachShader(program, shader.getShader());
}
for (Entry<String, Integer> entry : attributes.entrySet()) {
gl.glBindAttribLocation(program, entry.getValue(), entry.getKey());
}
gl.glLinkProgram(program);
int[] linked = new int[1];
gl.glGetProgramiv(program, GL2ES2.GL_LINK_STATUS, linked, 0);
if (linked[0] != GL.GL_TRUE) {
int[] logLength = new int[1];
gl.glGetProgramiv(program, GL2ES2.GL_INFO_LOG_LENGTH, logLength, 0);
byte[] logBytes = new byte[logLength[0]];
if (logLength[0] > 0) {
gl.glGetProgramInfoLog(program, logLength[0], (int[]) null, 0, logBytes, 0);
}
throw new GLException("Link error:\n" + new String(logBytes));
}
gl.glValidateProgram(program);
int[] valid = new int[1];
gl.glGetProgramiv(program, GL2ES2.GL_VALIDATE_STATUS, valid, 0);
if (valid[0] != GL.GL_TRUE) {
int[] logLength = new int[1];
gl.glGetProgramiv(program, GL2ES2.GL_INFO_LOG_LENGTH, logLength, 0);
byte[] logBytes = new byte[logLength[0]];
if (logLength[0] > 0) {
gl.glGetProgramInfoLog(program, logLength[0], (int[]) null, 0, logBytes, 0);
}
System.err.println(new String(logBytes));
throw new GLException("Validation error:\n" + new String(logBytes));
}
this.valid = true;
}
catch (Exception e) {
if (program != 0) {
gl.glDeleteProgram(program);
program = 0;
}
}
}
@Override
public void dispose() {
valid = false;
if (vbo != null) {
gl.glDeleteBuffers(vbo.length, vbo, 0);
vbo = null;
}
if (program != 0) {
int[] count = new int[1];
int[] shaders = new int[1];
while (true) {
gl.glGetAttachedShaders(program, 1, count, 0, shaders, 0);
if (count[0] == 0) {
break;
}
gl.glDetachShader(program, shaders[0]);
}
gl.glDeleteProgram(program);
program = 0;
}
}
public int getAttribute(String name) {
if (!valid) {
throw new GLException("Program not linked.");
}
Integer value = attributes.get(name);
if (value == null) {
throw new GLException("Attribute not bound: " + name);
}
return value;
}
public int getUniform(String name) throws GLException {
if (!valid) {
throw new GLException("Program not linked.");
}
int value = gl.glGetUniformLocation(program, name);
if (value == -1) {
throw new GLException("Uniform not recognized: " + name);
}
return value;
}
public void use() throws GLException {
if (!valid) {
throw new GLException("Program not linked.");
}
int[] value = new int[1];
gl.glGetProgramiv(program, GL2ES2.GL_VALIDATE_STATUS, value, 0);
if (value[0] != GL.GL_TRUE) {
link();
}
gl.glUseProgram(program);
if (vbo == null || vbo.length < openAttribute) {
if (vbo != null) {
gl.glDeleteBuffers(vbo.length, vbo, 0);
}
vbo = new int[openAttribute];
gl.glGenBuffers(vbo.length, vbo, 0);
}
}
public void bindBufferData(int target, String name, int numberOfUnits, FloatBuffer values, int usage, int unitSize,
boolean normalized) {
if (!valid) {
throw new GLException("Program not linked.");
}
Preconditions.checkPositionIndex(values.position() + numberOfUnits * unitSize, values.limit());
int attribute = getAttribute(name);
boolean refereshedVBO = false;
while (true) {
try {
gl.glBindBuffer(target, vbo[attribute - 1]);
gl.glBufferData(target, Buffers.SIZEOF_FLOAT * numberOfUnits * unitSize, values, usage);
if (gl.glGetError() == GL.GL_INVALID_OPERATION) {
throw new GLException("GL-Error 0x502");
}
gl.glVertexAttribPointer(attribute, unitSize, GL.GL_FLOAT, normalized, 0, 0);
gl.glEnableVertexAttribArray(attribute);
gl.glBindBuffer(target, 0);
return;
}
catch (GLException e) {
if (!refereshedVBO) {
refereshedVBO = true;
gl.glDeleteBuffers(1, vbo, attribute - 1);
gl.glGenBuffers(1, vbo, attribute - 1);
continue;
}
throw e;
}
}
}
public void bindBufferData(int target, String name, int numberOfUnits, IntBuffer values, int usage, int unitSize,
boolean normalized) {
if (!valid) {
throw new GLException("Program not linked.");
}
Preconditions.checkPositionIndex(values.position() + numberOfUnits * unitSize, values.limit());
int attribute = getAttribute(name);
boolean refereshedVBO = false;
while (true) {
try {
gl.glBindBuffer(target, vbo[attribute - 1]);
gl.glBufferData(target, Buffers.SIZEOF_INT * numberOfUnits * unitSize, values, usage);
if (gl.glGetError() == GL.GL_INVALID_OPERATION) {
throw new GLException("GL-Error 0x502");
}
gl.glVertexAttribPointer(attribute, unitSize, GL2ES2.GL_INT, normalized, 0, 0);
gl.glEnableVertexAttribArray(attribute);
gl.glBindBuffer(target, 0);
return;
}
catch (GLException e) {
if (!refereshedVBO) {
refereshedVBO = true;
gl.glDeleteBuffers(1, vbo, attribute - 1);
gl.glGenBuffers(1, vbo, attribute - 1);
continue;
}
throw e;
}
}
}
public void done() throws GLException {
if (!valid) {
throw new GLException("Program not linked.");
}
for (int attribute : attributes.values()) {
gl.glDisableVertexAttribArray(attribute);
}
gl.glUseProgram(0);
}
}