/*
* 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.util.*;
import javax.media.opengl.*;
import haven.glsl.ShaderMacro;
import static haven.GOut.checkerr;
public abstract class GLState {
public abstract void apply(GOut g);
public abstract void unapply(GOut g);
public abstract void prep(Buffer buf);
public void applyfrom(GOut g, GLState from) {
throw(new RuntimeException("Called applyfrom on non-conformant GLState (" + from + " -> " + this + ")"));
}
public void applyto(GOut g, GLState to) {
}
public void reapply(GOut g) {
}
public ShaderMacro[] shaders() {
return(null);
}
public boolean reqshaders() {
return(false);
}
public int capply() {
return(10);
}
public int cunapply() {
return(1);
}
public int capplyfrom(GLState from) {
return(-1);
}
public int capplyto(GLState to) {
return(0);
}
private static int slotnum = 0;
private static Slot<?>[] deplist = new Slot<?>[0];
private static Slot<?>[] idlist = new Slot<?>[0];
public static class Slot<T extends GLState> {
private static boolean dirty = false;
private static Collection<Slot<?>> all = new LinkedList<Slot<?>>();
public final Type type;
public final int id;
public final Class<T> scl;
private int depid = -1;
private final Slot<?>[] dep, rdep;
private Slot<?>[] grdep;
public static enum Type {
SYS, GEOM, DRAW
}
public Slot(Type type, Class<T> scl, Slot<?>[] dep, Slot<?>[] rdep) {
this.type = type;
this.scl = scl;
synchronized(Slot.class) {
this.id = slotnum++;
dirty = true;
Slot<?>[] nlist = new Slot<?>[slotnum];
System.arraycopy(idlist, 0, nlist, 0, idlist.length);
nlist[this.id] = this;
idlist = nlist;
all.add(this);
}
if(dep == null)
this.dep = new Slot<?>[0];
else
this.dep = dep;
if(rdep == null)
this.rdep = new Slot<?>[0];
else
this.rdep = rdep;
for(Slot<?> ds : this.dep) {
if(ds == null)
throw(new NullPointerException());
}
for(Slot<?> ds : this.rdep) {
if(ds == null)
throw(new NullPointerException());
}
}
public Slot(Type type, Class<T> scl, Slot... dep) {
this(type, scl, dep, null);
}
private static void makedeps(Collection<Slot<?>> slots) {
Map<Slot<?>, Set<Slot<?>>> lrdep = new HashMap<Slot<?>, Set<Slot<?>>>();
for(Slot<?> s : slots)
lrdep.put(s, new HashSet<Slot<?>>());
for(Slot<?> s : slots) {
lrdep.get(s).addAll(Arrays.asList(s.rdep));
for(Slot<?> ds : s.dep)
lrdep.get(ds).add(s);
}
Set<Slot<?>> left = new HashSet<Slot<?>>(slots);
final Map<Slot<?>, Integer> order = new HashMap<Slot<?>, Integer>();
int id = left.size() - 1;
Slot<?>[] cp = new Slot<?>[0];
while(!left.isEmpty()) {
boolean err = true;
fin:
for(Iterator<Slot<?>> i = left.iterator(); i.hasNext();) {
Slot<?> s = i.next();
for(Slot<?> ds : lrdep.get(s)) {
if(left.contains(ds))
continue fin;
}
err = false;
order.put(s, s.depid = id--);
Set<Slot<?>> grdep = new HashSet<Slot<?>>();
for(Slot<?> ds : lrdep.get(s)) {
grdep.add(ds);
for(Slot<?> ds2 : ds.grdep)
grdep.add(ds2);
}
s.grdep = grdep.toArray(cp);
i.remove();
}
if(err)
throw(new RuntimeException("Cycle encountered while compiling state slot dependencies"));
}
Comparator<Slot> cmp = new Comparator<Slot>() {
public int compare(Slot a, Slot b) {
return(order.get(a) - order.get(b));
}
};
for(Slot<?> s : slots)
Arrays.sort(s.grdep, cmp);
}
public static void update() {
synchronized(Slot.class) {
if(!dirty)
return;
makedeps(all);
deplist = new Slot<?>[all.size()];
for(Slot s : all)
deplist[s.depid] = s;
dirty = false;
}
}
public String toString() {
return("Slot<" + scl.getName() + ">");
}
}
public static class Buffer {
private GLState[] states = new GLState[slotnum];
public final GLConfig cfg;
public Buffer(GLConfig cfg) {
this.cfg = cfg;
}
public Buffer copy() {
Buffer ret = new Buffer(cfg);
System.arraycopy(states, 0, ret.states, 0, states.length);
return(ret);
}
public void copy(Buffer dest) {
dest.adjust();
System.arraycopy(states, 0, dest.states, 0, states.length);
for(int i = states.length; i < dest.states.length; i++)
dest.states[i] = null;
}
public void copy(Buffer dest, Slot.Type type) {
dest.adjust();
adjust();
for(int i = 0; i < states.length; i++) {
if(idlist[i].type == type)
dest.states[i] = states[i];
}
}
private void adjust() {
if(states.length < slotnum) {
GLState[] n = new GLState[slotnum];
System.arraycopy(states, 0, n, 0, states.length);
this.states = n;
}
}
public <T extends GLState> void put(Slot<? super T> slot, T state) {
if(states.length <= slot.id)
adjust();
states[slot.id] = state;
}
@SuppressWarnings("unchecked")
public <T extends GLState> T get(Slot<T> slot) {
if(states.length <= slot.id)
return(null);
return((T)states[slot.id]);
}
public boolean equals(Object o) {
if(!(o instanceof Buffer))
return(false);
Buffer b = (Buffer)o;
adjust();
b.adjust();
for(int i = 0; i < states.length; i++) {
if(!states[i].equals(b.states[i]))
return(false);
}
return(true);
}
public String toString() {
StringBuilder buf = new StringBuilder();
buf.append('[');
for(int i = 0; i < states.length; i++) {
if(i > 0)
buf.append(", ");
if(states[i] == null)
buf.append("null");
else
buf.append(states[i].toString());
}
buf.append(']');
return(buf.toString());
}
/* Should be used very, very sparingly. */
GLState[] states() {
return(states);
}
}
public static int bufdiff(Buffer f, Buffer t, boolean[] trans, boolean[] repl) {
Slot.update();
int cost = 0;
f.adjust(); t.adjust();
if(trans != null) {
for(int i = 0; i < trans.length; i++) {
trans[i] = false;
repl[i] = false;
}
}
for(int i = 0; i < f.states.length; i++) {
if(((f.states[i] == null) != (t.states[i] == null)) ||
((f.states[i] != null) && (t.states[i] != null) && !f.states[i].equals(t.states[i]))) {
if(!repl[i]) {
int cat = -1, caf = -1;
if((t.states[i] != null) && (f.states[i] != null)) {
cat = f.states[i].capplyto(t.states[i]);
caf = t.states[i].capplyfrom(f.states[i]);
}
if((cat >= 0) && (caf >= 0)) {
cost += cat + caf;
if(trans != null)
trans[i] = true;
} else {
if(f.states[i] != null)
cost += f.states[i].cunapply();
if(t.states[i] != null)
cost += t.states[i].capply();
if(trans != null)
repl[i] = true;
}
}
for(Slot ds : idlist[i].grdep) {
int id = ds.id;
if(repl[id])
continue;
if(trans != null)
repl[id] = true;
if(t.states[id] != null)
cost += t.states[id].cunapply();
if(f.states[id] != null)
cost += f.states[id].capply();
}
}
}
return(cost);
}
public static class TexUnit {
private final Applier st;
public final int id;
private TexUnit(Applier st, int id) {
this.st = st;
this.id = id;
}
public void act() {
st.texunit(id);
}
public void free() {
if(st.textab[id] != null)
throw(new RuntimeException("Texunit " + id + " freed twice"));
st.textab[id] = this;
}
public void ufree() {
act();
st.gl.glBindTexture(GL.GL_TEXTURE_2D, 0);
free();
}
}
public static class Applier {
public static boolean debug = false;
private Buffer old, cur, next;
public final GL2 gl;
public final GLConfig cfg;
private boolean[] trans = new boolean[0], repl = new boolean[0], adirty = new boolean[0];
private ShaderMacro[][] shaders = new ShaderMacro[0][], nshaders = new ShaderMacro[0][];
private int proghash = 0, nproghash = 0;
public ShaderMacro.Program prog;
public boolean usedprog;
public boolean pdirty = false, sdirty = false;
public long time = 0;
/* It seems ugly to treat these so specially, but right now I
* cannot see any good alternative. */
public Matrix4f cam = Matrix4f.id, wxf = Matrix4f.id, mv = Matrix4f.identity();
private Matrix4f ccam = null, cwxf = null;
public Applier(GL2 gl, GLConfig cfg) {
this.gl = gl;
this.cfg = cfg;
this.old = new Buffer(cfg);
this.cur = new Buffer(cfg);
this.next = new Buffer(cfg);
}
public <T extends GLState> void put(Slot<? super T> slot, T state) {
next.put(slot, state);
}
public <T extends GLState> T get(Slot<T> slot) {
return(next.get(slot));
}
public <T extends GLState> T cur(Slot<T> slot) {
return(cur.get(slot));
}
public <T extends GLState> T old(Slot<T> slot) {
return(old.get(slot));
}
public void prep(GLState st) {
st.prep(next);
}
public void set(Buffer to) {
to.copy(next);
}
public void copy(Buffer dest) {
next.copy(dest);
}
public Buffer copy() {
return(next.copy());
}
public void apply(GOut g) {
long st = 0;
if(Config.profile) st = System.nanoTime();
if(trans.length < slotnum) {
synchronized(Slot.class) {
trans = new boolean[slotnum];
repl = new boolean[slotnum];
shaders = Utils.extend(shaders, slotnum);
nshaders = Utils.extend(shaders, slotnum);
}
}
bufdiff(cur, next, trans, repl);
Slot<?>[] deplist = GLState.deplist;
nproghash = proghash;
for(int i = trans.length - 1; i >= 0; i--) {
nshaders[i] = shaders[i];
if(repl[i] || trans[i]) {
GLState nst = next.states[i];
ShaderMacro[] ns = (nst == null)?null:nst.shaders();
if(ns != nshaders[i]) {
nproghash ^= System.identityHashCode(nshaders[i]) ^ System.identityHashCode(ns);
nshaders[i] = ns;
sdirty = true;
}
}
}
usedprog = prog != null;
if(sdirty) {
ShaderMacro.Program np;
boolean usesl;
switch(g.gc.pref.progmode.val) {
case ALWAYS:
usesl = true;
break;
case REQ:
usesl = false;
for(int i = 0; i < trans.length; i++) {
GLState nst = next.states[i];
if((nshaders[i] != null) && (nst != null) && nst.reqshaders()) {
usesl = true;
break;
}
}
break;
case NEVER:
default: /* ¦] */
usesl = false;
break;
}
if(usesl) {
np = findprog(nproghash, nshaders);
} else {
np = null;
}
if(np != prog) {
if(np != null)
np.apply(g);
else
g.gl.glUseProgramObjectARB(0);
prog = np;
if(debug)
checkerr(g.gl);
pdirty = true;
}
}
if((prog != null) != usedprog) {
for(int i = 0; i < trans.length; i++) {
if(trans[i])
repl[i] = true;
}
}
cur.copy(old);
for(int i = deplist.length - 1; i >= 0; i--) {
int id = deplist[i].id;
if(repl[id]) {
if(cur.states[id] != null) {
cur.states[id].unapply(g);
if(debug)
stcheckerr(g, "unapply", cur.states[id]);
}
cur.states[id] = null;
proghash ^= System.identityHashCode(shaders[id]);
shaders[id] = null;
}
}
/* Note on invariants: States may exit non-locally
* from apply, applyto/applyfrom or reapply (e.g. by
* throwing Loading exceptions) in a defined way
* provided they do so before they have changed any GL
* state. If they exit non-locally after GL state has
* been altered, future results are undefined. */
for(int i = 0; i < deplist.length; i++) {
int id = deplist[i].id;
if(repl[id]) {
if(next.states[id] != null) {
next.states[id].apply(g);
cur.states[id] = next.states[id];
proghash ^= System.identityHashCode(shaders[id]) ^ System.identityHashCode(nshaders[id]);
shaders[id] = nshaders[id];
if(debug)
stcheckerr(g, "apply", cur.states[id]);
}
if(!pdirty && (prog != null))
prog.adirty(deplist[i]);
} else if(trans[id]) {
cur.states[id].applyto(g, next.states[id]);
if(debug)
stcheckerr(g, "applyto", cur.states[id]);
next.states[id].applyfrom(g, cur.states[id]);
cur.states[id] = next.states[id];
proghash ^= System.identityHashCode(shaders[id]) ^ System.identityHashCode(nshaders[id]);
shaders[id] = nshaders[id];
if(debug)
stcheckerr(g, "applyfrom", cur.states[id]);
if(!pdirty && (prog != null))
prog.adirty(deplist[i]);
} else if((prog != null) && pdirty && (shaders[id] != null)) {
cur.states[id].reapply(g);
if(debug)
stcheckerr(g, "reapply", cur.states[id]);
}
}
if((ccam != cam) || (cwxf != wxf)) {
/* See comment above */
mv.load(ccam = cam).mul1(cwxf = wxf);
matmode(GL2.GL_MODELVIEW);
gl.glLoadMatrixf(mv.m, 0);
}
if(prog != null)
prog.autoapply(g, pdirty);
pdirty = sdirty = false;
checkerr(gl);
if(Config.profile)
time += System.nanoTime() - st;
}
public static class ApplyException extends RuntimeException {
public final transient GLState st;
public final String func;
public ApplyException(String func, GLState st, Throwable cause) {
super("Error in " + func + " of " + st, cause);
this.st = st;
this.func = func;
}
}
private void stcheckerr(GOut g, String func, GLState st) {
try {
checkerr(g.gl);
} catch(RuntimeException e) {
throw(new ApplyException(func, st, e));
}
}
/* "Meta-states" */
private int matmode = GL2.GL_MODELVIEW;
private int texunit = 0;
public void matmode(int mode) {
if(mode != matmode) {
gl.glMatrixMode(mode);
matmode = mode;
}
}
public void texunit(int unit) {
if(unit != texunit) {
gl.glActiveTexture(GL.GL_TEXTURE0 + unit);
texunit = unit;
}
}
private TexUnit[] textab = new TexUnit[0];
public TexUnit texalloc() {
int i;
for(i = 0; i < textab.length; i++) {
if(textab[i] != null) {
TexUnit ret = textab[i];
textab[i] = null;
return(ret);
}
}
textab = new TexUnit[i + 1];
return(new TexUnit(this, i));
}
public TexUnit texalloc(GOut g, TexGL tex) {
TexUnit ret = texalloc();
ret.act();
gl.glBindTexture(GL.GL_TEXTURE_2D, tex.glid(g));
return(ret);
}
public TexUnit texalloc(GOut g, TexMS tex) {
TexUnit ret = texalloc();
ret.act();
gl.glBindTexture(GL3.GL_TEXTURE_2D_MULTISAMPLE, tex.glid(g));
return(ret);
}
/* Program internation */
public static class SavedProg {
public final int hash;
public final ShaderMacro.Program prog;
public final ShaderMacro[][] shaders;
public SavedProg next;
boolean used = true;
public SavedProg(int hash, ShaderMacro.Program prog, ShaderMacro[][] shaders) {
this.hash = hash;
this.prog = prog;
this.shaders = Utils.splice(shaders, 0);
}
}
private SavedProg[] ptab = new SavedProg[32];
private int nprog = 0;
private long lastclean = System.currentTimeMillis();
private ShaderMacro.Program findprog(int hash, ShaderMacro[][] shaders) {
int idx = hash & (ptab.length - 1);
outer: for(SavedProg s = ptab[idx]; s != null; s = s.next) {
if(s.hash != hash)
continue;
int i;
for(i = 0; i < s.shaders.length; i++) {
if(shaders[i] != s.shaders[i])
continue outer;
}
for(; i < shaders.length; i++) {
if(shaders[i] != null)
continue outer;
}
s.used = true;
return(s.prog);
}
Collection<ShaderMacro> mods = new LinkedList<ShaderMacro>();
for(int i = 0; i < shaders.length; i++) {
if(shaders[i] == null)
continue;
for(int o = 0; o < shaders[i].length; o++)
mods.add(shaders[i][o]);
}
ShaderMacro.Program prog = ShaderMacro.Program.build(mods);
SavedProg s = new SavedProg(hash, prog, shaders);
s.next = ptab[idx];
ptab[idx] = s;
nprog++;
if(nprog > ptab.length)
rehash(ptab.length * 2);
return(prog);
}
private void rehash(int nlen) {
SavedProg[] ntab = new SavedProg[nlen];
for(int i = 0; i < ptab.length; i++) {
while(ptab[i] != null) {
SavedProg s = ptab[i];
ptab[i] = s.next;
int ni = s.hash & (ntab.length - 1);
s.next = ntab[ni];
ntab[ni] = s;
}
}
ptab = ntab;
}
public void clean() {
long now = System.currentTimeMillis();
if(now - lastclean > 60000) {
for(int i = 0; i < ptab.length; i++) {
SavedProg c, p;
for(c = ptab[i], p = null; c != null; p = c, c = c.next) {
if(!c.used) {
if(p != null)
p.next = c.next;
else
ptab[i] = c.next;
c.prog.dispose();
nprog--;
} else {
c.used = false;
}
}
}
/* XXX: Rehash into smaller table? It's probably not a
* problem, but it might be nice just for
* completeness. */
lastclean = now;
}
}
public int numprogs() {
return(nprog);
}
}
private class Wrapping implements Rendered {
private final Rendered r;
private Wrapping(Rendered r) {
if(r == null)
throw(new NullPointerException("Wrapping null in " + GLState.this));
this.r = r;
}
public void draw(GOut g) {}
public boolean setup(RenderList rl) {
rl.add(r, GLState.this);
return(false);
}
}
public Rendered apply(Rendered r) {
return(new Wrapping(r));
}
public static abstract class Abstract extends GLState {
public void apply(GOut g) {}
public void unapply(GOut g) {}
}
public static class Composed extends Abstract {
public final GLState[] states;
public Composed(GLState... states) {
for(GLState st : states) {
if(st == null)
throw(new RuntimeException("null state in list of " + Arrays.asList(states)));
}
this.states = states;
}
public boolean equals(Object o) {
if(!(o instanceof Composed))
return(false);
return(Arrays.equals(states, ((Composed)o).states));
}
public int hashCode() {
return(Arrays.hashCode(states));
}
public void prep(Buffer buf) {
for(GLState st : states)
st.prep(buf);
}
}
public static GLState compose(GLState... states) {
return(new Composed(states));
}
public static class Delegate extends Abstract {
public GLState del;
public Delegate(GLState del) {
this.del = del;
}
public void prep(Buffer buf) {
del.prep(buf);
}
}
public interface GlobalState {
public Global global(RenderList r, Buffer ctx);
}
public interface Global {
public void postsetup(RenderList rl);
public void prerender(RenderList rl, GOut g);
public void postrender(RenderList rl, GOut g);
}
public static abstract class StandAlone extends GLState {
public final Slot<StandAlone> slot;
public StandAlone(Slot.Type type, Slot<?>... dep) {
slot = new Slot<StandAlone>(type, StandAlone.class, dep);
}
public void prep(Buffer buf) {
buf.put(slot, this);
}
}
public static final GLState nullstate = new GLState() {
public void apply(GOut g) {}
public void unapply(GOut g) {}
public void prep(Buffer buf) {}
};
static {
Console.setscmd("applydb", new Console.Command() {
public void run(Console cons, String[] args) {
Applier.debug = Utils.parsebool(args[1], false);
}
});
}
}