/* * 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 haven.glsl.*; import java.awt.Color; import java.util.*; import java.awt.image.*; import java.nio.*; import javax.media.opengl.*; import static haven.GOut.checkerr; public abstract class TexGL extends Tex { public static boolean disableall = false; private static final WeakList<TexGL> active = new WeakList<TexGL>(); protected TexOb t = null; protected boolean mipmap = false, centroid = false; protected int magfilter = GL.GL_NEAREST, minfilter = GL.GL_NEAREST, wrapmode = GL.GL_REPEAT; protected Coord tdim; private final Object idmon = new Object(); private WeakList.Entry<TexGL> actref; private boolean setparams = true; public static class TexOb extends GLObject { public final int id; public TexOb(GL2 gl) { super(gl); int[] buf = new int[1]; gl.glGenTextures(1, buf, 0); this.id = buf[0]; } protected void delete() { int[] buf = {id}; gl.glDeleteTextures(1, buf, 0); } } public static final ShaderMacro mkcentroid = new ShaderMacro() { public void modify(ProgramContext prog) { Tex2D.get(prog).ipol = Varying.Interpol.CENTROID; } }; public static GLState.TexUnit lbind(GOut g, TexGL tex) { GLState.TexUnit sampler = g.st.texalloc(); sampler.act(); try { g.gl.glBindTexture(GL.GL_TEXTURE_2D, tex.glid(g)); return(sampler); } catch(Loading l) { sampler.free(); throw(l); } } public static class TexDraw extends GLState { public static final Slot<TexDraw> slot = new Slot<TexDraw>(Slot.Type.DRAW, TexDraw.class, HavenPanel.global); private static final ShaderMacro[] nshaders = {Tex2D.mod}; private static final ShaderMacro[] cshaders = {Tex2D.mod, mkcentroid}; public final TexGL tex; private TexUnit sampler; public TexDraw(TexGL tex) { this.tex = tex; } public void prep(Buffer buf) { buf.put(slot, this); } public void apply(GOut g) { GL2 gl = g.gl; sampler = lbind(g, tex); if(g.st.prog != null) { reapply(g); } else { gl.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_TEXTURE_ENV_MODE, GL2.GL_MODULATE); gl.glEnable(GL.GL_TEXTURE_2D); } } public void reapply(GOut g) { GL2 gl = g.gl; gl.glUniform1i(g.st.prog.uniform(Tex2D.tex2d), sampler.id); } public void unapply(GOut g) { GL2 gl = g.gl; sampler.act(); if(!g.st.usedprog) gl.glDisable(GL.GL_TEXTURE_2D); sampler.free(); sampler = null; } public ShaderMacro[] shaders() { /* XXX: This combinatorial stuff does not seem quite right. */ if(tex.centroid) return(cshaders); else return(nshaders); } public int capply() { return(100); } public int capplyfrom(GLState from) { if(from instanceof TexDraw) return(99); return(-1); } public void applyfrom(GOut g, GLState sfrom) { GL2 gl = g.gl; TexDraw from = (TexDraw)sfrom; from.sampler.act(); int glid = tex.glid(g); sampler = from.sampler; from.sampler = null; gl.glBindTexture(GL.GL_TEXTURE_2D, glid); if(g.st.pdirty) reapply(g); } public String toString() { return("TexDraw(" + tex + ")"); } } private final TexDraw draw = new TexDraw(this); public GLState draw() {return(draw);} public static class TexClip extends GLState { public static final Slot<TexClip> slot = new Slot<TexClip>(Slot.Type.GEOM, TexClip.class, HavenPanel.global, TexDraw.slot); private static final ShaderMacro[] shaders = {Tex2D.clip}; public final TexGL tex; private TexUnit sampler; public TexClip(TexGL tex) { this.tex = tex; } private void fapply(GOut g) { GL2 gl = g.gl; TexDraw draw = g.st.get(TexDraw.slot); if(draw == null) { sampler = lbind(g, tex); gl.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_TEXTURE_ENV_MODE, GL2.GL_COMBINE); gl.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_COMBINE_RGB, GL2.GL_REPLACE); gl.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_SRC0_RGB, GL2.GL_PREVIOUS); gl.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_OPERAND0_RGB, GL2.GL_SRC_COLOR); gl.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_COMBINE_ALPHA, GL2.GL_MODULATE); gl.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_SRC0_ALPHA, GL2.GL_PREVIOUS); gl.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_OPERAND0_ALPHA, GL2.GL_SRC_ALPHA); gl.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2GL3.GL_SRC1_ALPHA, GL2.GL_TEXTURE); gl.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_OPERAND1_ALPHA, GL2.GL_SRC_ALPHA); gl.glEnable(GL2.GL_TEXTURE_2D); } else { if(draw.tex != this.tex) throw(new RuntimeException("TexGL does not support different clip and draw textures.")); } gl.glEnable(GL2.GL_ALPHA_TEST); } private void papply(GOut g) { TexDraw draw = g.st.get(TexDraw.slot); if(draw == null) { sampler = lbind(g, tex); } else { if(draw.tex != this.tex) throw(new RuntimeException("TexGL does not support different clip and draw textures.")); } } public void apply(GOut g) { if(g.st.prog == null) fapply(g); else papply(g); if(g.gc.pref.alphacov.val) { g.gl.glEnable(GL2.GL_SAMPLE_ALPHA_TO_COVERAGE); g.gl.glEnable(GL2.GL_SAMPLE_ALPHA_TO_ONE); } } private void funapply(GOut g) { GL2 gl = g.gl; if(g.st.old(TexDraw.slot) == null) { sampler.act(); gl.glDisable(GL2.GL_TEXTURE_2D); sampler.free(); sampler = null; } gl.glDisable(GL2.GL_ALPHA_TEST); } private void punapply(GOut g) { GL2 gl = g.gl; if(g.st.old(TexDraw.slot) == null) { sampler.act(); sampler.free(); sampler = null; } } public void unapply(GOut g) { if(!g.st.usedprog) funapply(g); else punapply(g); if(g.gc.pref.alphacov.val) { g.gl.glDisable(GL2.GL_SAMPLE_ALPHA_TO_COVERAGE); g.gl.glDisable(GL2.GL_SAMPLE_ALPHA_TO_ONE); } } public ShaderMacro[] shaders() { return(shaders); } public int capply() { return(100); } public int capplyfrom(GLState from) { if(from instanceof TexClip) return(99); return(-1); } public void applyfrom(GOut g, GLState sfrom) { TexDraw draw = g.st.get(TexDraw.slot), old = g.st.old(TexDraw.slot); if((old != null) || (draw != null)) throw(new RuntimeException("TexClip is somehow being transition even though there is a TexDraw")); GL2 gl = g.gl; TexClip from = (TexClip)sfrom; from.sampler.act(); int glid = tex.glid(g); sampler = from.sampler; from.sampler = null; gl.glBindTexture(GL2.GL_TEXTURE_2D, glid); } public void prep(Buffer buf) { buf.put(slot, this); } public String toString() { return("TexClip(" + tex + ")"); } } private final TexClip clip = new TexClip(this); public GLState clip() {return(clip);} public TexGL(Coord sz, Coord tdim) { super(sz); this.tdim = tdim; } public TexGL(Coord sz) { this(sz, new Coord(nextp2(sz.x), nextp2(sz.y))); } protected abstract void fill(GOut gl); public static int num() { synchronized(active) { return(active.size()); } } public static void setallparams() { synchronized(active) { for(TexGL tex : active) tex.setparams = true; } } protected void setparams(GOut g) { GL gl = g.gl; gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, minfilter); gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, magfilter); if((minfilter == GL.GL_LINEAR_MIPMAP_LINEAR) && (g.gc.pref.anisotex.val >= 1)) gl.glTexParameterf(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAX_ANISOTROPY_EXT, Math.max(g.gc.pref.anisotex.val, 1.0f)); gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_S, wrapmode); gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_T, wrapmode); } private void create(GOut g) { GL2 gl = g.gl; t = new TexOb(gl); gl.glBindTexture(GL.GL_TEXTURE_2D, t.id); setparams(g); try { fill(g); } catch(Loading l) { t.dispose(); t = null; throw(l); } try { checkerr(gl); } catch(GOut.GLOutOfMemoryException e) { throw(new RuntimeException("Out of memory when creating texture " + this, e)); } } public float tcx(int x) { return(((float)x) / ((float)tdim.x)); } public float tcy(int y) { return(((float)y) / ((float)tdim.y)); } @Deprecated public void mipmap() { mipmap = true; minfilter = GL.GL_LINEAR_MIPMAP_LINEAR; dispose(); } public void magfilter(int filter) { magfilter = filter; setparams = true; } public void minfilter(int filter) { minfilter = filter; setparams = true; } public void wrapmode(int mode) { wrapmode = mode; setparams = true; } public int glid(GOut g) { GL gl = g.gl; synchronized(idmon) { if((t != null) && (t.gl != gl)) dispose(); if(t == null) { create(g); synchronized(active) { actref = active.add2(this); } } else if(setparams) { gl.glBindTexture(GL.GL_TEXTURE_2D, t.id); setparams(g); setparams = false; } return(t.id); } } public void render(GOut g, Coord c, Coord ul, Coord br, Coord sz) { GL2 gl = g.gl; g.st.prep(draw); g.apply(); checkerr(gl); if(!disableall) { gl.glBegin(GL2.GL_QUADS); float l = ((float)ul.x) / ((float)tdim.x); float t = ((float)ul.y) / ((float)tdim.y); float r = ((float)br.x) / ((float)tdim.x); float b = ((float)br.y) / ((float)tdim.y); gl.glTexCoord2f(l, t); gl.glVertex3i(c.x, c.y, 0); gl.glTexCoord2f(r, t); gl.glVertex3i(c.x + sz.x, c.y, 0); gl.glTexCoord2f(r, b); gl.glVertex3i(c.x + sz.x, c.y + sz.y, 0); gl.glTexCoord2f(l, b); gl.glVertex3i(c.x, c.y + sz.y, 0); gl.glEnd(); checkerr(gl); } } public void dispose() { synchronized(idmon) { if(t != null) { t.dispose(); t = null; synchronized(active) { actref.remove(); actref = null; } } } } public BufferedImage get(GOut g, boolean invert) { GL2 gl = g.gl; g.state2d(); g.apply(); GLState.TexUnit s = g.st.texalloc(); s.act(); gl.glBindTexture(GL.GL_TEXTURE_2D, glid(g)); byte[] buf = new byte[tdim.x * tdim.y * 4]; gl.glGetTexImage(GL.GL_TEXTURE_2D, 0, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, ByteBuffer.wrap(buf)); s.free(); GOut.checkerr(gl); if(invert) { for(int y = 0; y < tdim.y / 2; y++) { int to = y * tdim.x * 4, bo = (tdim.y - y - 1) * tdim.x * 4; for(int o = 0; o < tdim.x * 4; o++, to++, bo++) { byte t = buf[to]; buf[to] = buf[bo]; buf[bo] = t; } } } WritableRaster raster = Raster.createInterleavedRaster(new DataBufferByte(buf, buf.length), tdim.x, tdim.y, 4 * tdim.x, 4, new int[] {0, 1, 2, 3}, null); return(new BufferedImage(TexI.glcm, raster, false, null)); } public BufferedImage get(GOut g) { return(get(g, true)); } @Material.ResName("tex") public static class $tex implements Material.ResCons2 { public void cons(final Resource res, List<GLState> states, List<Material.Res.Resolver> left, Object... args) { final Resource tres; final int tid; int a = 0; if(args[a] instanceof String) { tres = Resource.load((String)args[a], (Integer)args[a + 1]); tid = (Integer)args[a + 2]; a += 3; } else { tres = res; tid = (Integer)args[a]; a += 1; } boolean tclip = true; while(a < args.length) { String f = (String)args[a++]; if(f.equals("a")) tclip = false; } final boolean clip = tclip; /* ¦] */ left.add(new Material.Res.Resolver() { public void resolve(Collection<GLState> buf) { Tex tex; TexR rt = tres.layer(TexR.class, tid); if(rt != null) { tex = rt.tex(); } else { Resource.Image img = tres.layer(Resource.imgc, tid); if(img != null) { tex = img.tex(); } else { throw(new RuntimeException(String.format("Specified texture %d for %s not found in %s", tid, res, tres))); } } buf.add(tex.draw()); if(clip) buf.add(tex.clip()); } }); } } static { Console.setscmd("texdis", new Console.Command() { public void run(Console cons, String[] args) { disableall = (Integer.parseInt(args[1]) != 0); } }); } }