/*
* This file is part of the Haven & Hearth game client.
* Copyright (C) 2009 Fredrik Tolf <fredrik@dolda2000.com>, and
* Björn Johannessen <johannessen.bjorn@gmail.com>
*
* Redistribution and/or modification of this file is subject to the
* terms of the GNU Lesser General Public License, version 3, as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* Other parts of this source tree adhere to other copying
* rights. Please see the file `COPYING' in the root directory of the
* source tree for details.
*
* A copy the GNU Lesser General Public License is distributed along
* with the source tree of which this file is a part in the file
* `doc/LPGL-3'. If it is missing for any reason, please see the Free
* Software Foundation's website at <http://www.fsf.org/>, or write
* to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307 USA
*/
package haven;
import java.io.*;
import java.util.*;
import javax.media.opengl.*;
public abstract class GLShader implements java.io.Serializable {
public final String source, header;
private transient ShaderOb gls;
public GLShader(String source, String header) {
this.source = source;
this.header = header;
}
public static class ShaderOb extends GLObject {
public final int id;
public ShaderOb(GL2 gl, int type) {
super(gl);
id = gl.glCreateShaderObjectARB(type);
GOut.checkerr(gl);
}
protected void delete() {
gl.glDeleteObjectARB(id);
}
public void compile(GLShader sh) {
/* Does JOGL use the byte or char length or the supplied
* String, and in case of the former, how does one know
* the coding it encodes the String as so as to supply the
* corrent length? It won't matter since all reasonable
* programs will be ASCII, of course, but still... */
gl.glShaderSourceARB(id, 1, new String[] {sh.source}, new int[] {sh.source.length()}, 0);
gl.glCompileShaderARB(id);
int[] buf = {0};
gl.glGetObjectParameterivARB(id, GL2.GL_OBJECT_COMPILE_STATUS_ARB, buf, 0);
if(buf[0] != 1) {
String info = null;
gl.glGetObjectParameterivARB(id, GL2.GL_OBJECT_INFO_LOG_LENGTH_ARB, buf, 0);
if(buf[0] > 0) {
byte[] logbuf = new byte[buf[0]];
gl.glGetInfoLogARB(id, logbuf.length, buf, 0, logbuf, 0);
/* The "platform's default charset" is probably a reasonable choice. */
info = new String(logbuf, 0, buf[0]);
}
throw(new ShaderException("Failed to compile shader", sh, info));
}
}
}
public static class ShaderException extends RuntimeException {
public final GLShader shader;
public final String info;
public ShaderException(String msg, GLShader shader, String info) {
super(msg);
this.shader = shader;
this.info = info;
}
public String toString() {
if(info == null)
return(super.toString());
else
return(super.toString() + "\nLog:\n" + info);
}
}
public abstract static class Splitter {
private final BufferedReader in;
public final StringBuilder main = new StringBuilder();
public StringBuilder buf = main;
public Splitter(Reader r) {
in = new BufferedReader(r);
}
public Splitter(InputStream i) {
this(new InputStreamReader(i, Utils.ascii));
}
public void parse() throws IOException {
String ln;
while((ln = in.readLine()) != null) {
if(ln.startsWith("#pp ")) {
String d = ln.substring(4).trim();
String a = "";
int p = d.indexOf(' ');
if(p >= 0) {
a = d.substring(p + 1);
d = d.substring(0, p).trim();
}
d = d.intern();
directive(d, a);
} else {
buf.append(ln + "\n");
}
}
}
public abstract void directive(String directive, String args);
}
public static class VertexShader extends GLShader {
public final String entry;
public final String[] args;
public final int order;
public VertexShader(String source, String header, String entry, int order, String... args) {
super(source, header);
this.entry = entry;
this.order = order;
this.args = args;
}
public VertexShader(String source) {
this(source, "", null, 0);
}
protected ShaderOb create(GL2 gl) {
ShaderOb r = new ShaderOb(gl, GL2.GL_VERTEX_SHADER);
r.compile(this);
return(r);
}
private boolean uses(String arg) {
for(String a : args) {
if(a.equals(arg))
return(true);
}
return(false);
}
private String call() {
String ret = entry + "(";
boolean f = true;
for(String arg : args) {
if(!f)
ret += ", ";
ret += arg;
f = false;
}
ret += ")";
return(ret);
}
private final static Comparator<VertexShader> cmp = new Comparator<VertexShader>() {
public int compare(VertexShader a, VertexShader b) {
return(a.order - b.order);
}
};
public static VertexShader makemain(List<VertexShader> shaders) {
StringBuilder buf = new StringBuilder();
Collections.sort(shaders, cmp);
for(VertexShader sh : shaders)
buf.append(sh.header + "\n");
buf.append("\n");
buf.append("void main()\n{\n");
buf.append(" vec4 fcol = gl_Color;\n");
buf.append(" vec4 bcol = gl_Color;\n");
buf.append(" vec4 objv = gl_Vertex;\n");
buf.append(" vec3 objn = gl_Normal;\n");
int i = 0;
for(; i < shaders.size(); i++) {
VertexShader sh = shaders.get(i);
if(sh.uses("eyev") || sh.uses("eyen"))
break;
buf.append(" " + sh.call() + ";\n");
}
buf.append(" vec4 eyev = gl_ModelViewMatrix * objv;\n");
buf.append(" vec3 eyen = gl_NormalMatrix * objn;\n");
for(; i < shaders.size(); i++) {
VertexShader sh = shaders.get(i);
buf.append(" " + sh.call() + ";\n");
}
buf.append(" gl_FrontColor = fcol;\n");
buf.append(" gl_Position = gl_ProjectionMatrix * eyev;\n");
buf.append("}\n");
return(new VertexShader(buf.toString()));
}
public static VertexShader parse(Reader in) throws IOException {
class VSplitter extends Splitter {
StringBuilder header = new StringBuilder();
String entry;
String[] args;
int order = 0;
VSplitter(Reader in) {super(in);}
public void directive(String d, String a) {
if(d == "header") {
buf = header;
} else if(d == "main") {
buf = main;
} else if(d == "order") {
order = Integer.parseInt(a);
} else if(d == "entry") {
String[] args = a.split(" +");
entry = args[0];
this.args = new String[args.length - 1];
for(int i = 1, o = 0; i < args.length; i++, o++)
this.args[o] = args[i];
}
}
}
VSplitter p = new VSplitter(in);
p.parse();
if(p.entry == null)
throw(new RuntimeException("No entry specified in shader source."));
return(new VertexShader(p.main.toString(), p.header.toString(), p.entry, p.order, p.args));
}
public static VertexShader load(Class<?> base, String name) {
InputStream in = base.getResourceAsStream(name);
try {
try {
return(parse(new InputStreamReader(in, Utils.ascii)));
} finally {
in.close();
}
} catch(IOException e) {
throw(new RuntimeException(e));
}
}
}
public static class FragmentShader extends GLShader {
public final String entry;
public final int order;
public FragmentShader(String source, String header, String entry, int order) {
super(source, header);
this.entry = entry;
this.order = order;
}
public FragmentShader(String source) {
this(source, "", null, 0);
}
protected ShaderOb create(GL2 gl) {
ShaderOb r = new ShaderOb(gl, GL2.GL_FRAGMENT_SHADER);
r.compile(this);
return(r);
}
private final static Comparator<FragmentShader> cmp = new Comparator<FragmentShader>() {
public int compare(FragmentShader a, FragmentShader b) {
return(a.order - b.order);
}
};
public static FragmentShader makemain(List<FragmentShader> shaders) {
StringBuilder buf = new StringBuilder();
Collections.sort(shaders, cmp);
for(FragmentShader sh : shaders)
buf.append(sh.header + "\n");
buf.append("\n");
buf.append("void main()\n{\n");
buf.append(" vec4 res = gl_Color;\n");
for(FragmentShader sh : shaders) {
buf.append(" " + sh.entry + "(res);\n");
}
buf.append(" gl_FragColor = res;\n");
buf.append("}\n");
return(new FragmentShader(buf.toString()));
}
public static FragmentShader parse(Reader in) throws IOException {
class FSplitter extends Splitter {
StringBuilder header = new StringBuilder();
String entry;
int order = 0;
FSplitter(Reader in) {super(in);}
public void directive(String d, String a) {
if(d == "header") {
buf = header;
} else if(d == "main") {
buf = main;
} else if(d == "order") {
order = Integer.parseInt(a);
} else if(d == "entry") {
String[] args = a.split(" +");
entry = args[0];
}
}
}
FSplitter p = new FSplitter(in);
p.parse();
if(p.entry == null)
throw(new RuntimeException("No entry specified in shader source."));
return(new FragmentShader(p.main.toString(), p.header.toString(), p.entry, p.order));
}
public static FragmentShader load(Class<?> base, String name) {
InputStream in = base.getResourceAsStream(name);
try {
try {
return(parse(new InputStreamReader(in, Utils.ascii)));
} finally {
in.close();
}
} catch(IOException e) {
throw(new RuntimeException(e));
}
}
}
public int glid(GL2 gl) {
if((gls != null) && (gls.gl != gl)) {
gls.dispose();
gls = null;
}
if(gls == null)
gls = create(gl);
return(gls.id);
}
protected abstract ShaderOb create(GL2 gl);
}