Commits

Michael Ludwig  committed d76e769

Add FBO support to render to texture passes. Significant performance boost.

  • Participants
  • Parent commits 9a08c71

Comments (0)

Files changed (9)

File scratch/FBOTest.java

+import javax.media.opengl.GL;
+import javax.media.opengl.GLAutoDrawable;
+import javax.media.opengl.GLCanvas;
+import javax.swing.JFrame;
+
+import com.sun.opengl.util.FPSAnimator;
+
+
+public class FBOTest implements javax.media.opengl.GLEventListener {
+	public static void main(String[] args) {
+		JFrame frame = new JFrame();
+		GLCanvas canvas = new GLCanvas();
+		frame.add(canvas);
+		canvas.addGLEventListener(new FBOTest());
+		FPSAnimator anim = new FPSAnimator(canvas, 60);
+		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+		frame.setSize(500, 500);
+		frame.setVisible(true);
+		canvas.display();
+		System.exit(0);
+	}
+	
+	public void display(GLAutoDrawable arg0) {
+		
+	}
+
+	public void displayChanged(GLAutoDrawable arg0, boolean arg1, boolean arg2) {
+		// TODO Auto-generated method stub
+		
+	}
+
+	public void init(GLAutoDrawable arg0) {
+		GL gl = arg0.getGL();
+		int[] fboID = new int[1];
+		gl.glGenFramebuffersEXT(1, fboID, 0);
+		gl.glBindFramebufferEXT(GL.GL_FRAMEBUFFER_EXT, fboID[0]);
+		int[] rbs = new int[2];
+		/*gl.glGenRenderbuffersEXT(2, rbs, 0);
+		gl.glBindRenderbufferEXT(GL.GL_RENDERBUFFER_EXT, rbs[0]);
+		gl.glRenderbufferStorageEXT(GL.GL_RENDERBUFFER_EXT, GL.GL_STENCIL_INDEX, 512, 512);
+		gl.glFramebufferRenderbufferEXT(GL.GL_FRAMEBUFFER_EXT, GL.GL_STENCIL_ATTACHMENT_EXT, GL.GL_RENDERBUFFER_EXT, rbs[0]);
+		*/
+		gl.glBindRenderbufferEXT(GL.GL_RENDERBUFFER_EXT, rbs[1]);
+		gl.glRenderbufferStorageEXT(GL.GL_RENDERBUFFER_EXT, GL.GL_DEPTH_COMPONENT, 512, 512);
+		gl.glFramebufferRenderbufferEXT(GL.GL_FRAMEBUFFER_EXT, GL.GL_DEPTH_ATTACHMENT_EXT, GL.GL_RENDERBUFFER_EXT, rbs[1]);
+		
+		gl.glBindRenderbufferEXT(GL.GL_RENDERBUFFER_EXT, 0);
+		
+		int[] tex = new int[1];
+		gl.glGenTextures(1, tex, 0);
+		gl.glBindTexture(GL.GL_TEXTURE_2D, tex[0]);
+		gl.glTexImage2D(GL.GL_TEXTURE_2D, 0, GL.GL_RGB8, 512, 512, 0, GL.GL_RGB, GL.GL_UNSIGNED_BYTE, null);
+		gl.glBindTexture(GL.GL_TEXTURE_2D, 0);
+
+		gl.glFramebufferTexture2DEXT(GL.GL_FRAMEBUFFER_EXT, GL.GL_COLOR_ATTACHMENT0_EXT, GL.GL_TEXTURE_2D, tex[0], 0);
+		
+		int status = gl.glCheckFramebufferStatusEXT(GL.GL_FRAMEBUFFER_EXT);
+		if (status != GL.GL_FRAMEBUFFER_COMPLETE_EXT) {
+			String msg = "FBO failed completion test, unable to render";
+			switch(status) {
+			case GL.GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT: msg = "FBO attachments aren't complete"; break;
+			case GL.GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT: msg = "FBO needs at least one attachment"; break;
+			case GL.GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT: msg = "FBO draw buffers improperly enabled"; break;
+			case GL.GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT: msg = "FBO read buffer improperly enabled"; break;
+			case GL.GL_FRAMEBUFFER_UNSUPPORTED_EXT: msg = "Texture attachment formats aren't supported by this vendor"; break;
+			}
+			System.err.println("NOT COMPLETE: " + msg);
+		} else
+			System.out.println("FBO complete");
+	}
+
+	public void reshape(GLAutoDrawable arg0, int arg1, int arg2, int arg3, int arg4) {
+		// TODO Auto-generated method stub
+		
+	}
+
+}

File src/com/ferox/core/renderer/RenderToTexturePass.java

 	private Attachment[] colorArray;
 	private Attachment depthAttach;
 	
+	private boolean useStencilBuffer;
+	
 	public RenderToTexturePass() {
 		this(null, null);
 	}
 		
 		this.colorArray = new Attachment[1];
 		this.depthAttach = new Attachment();
+		this.useStencilBuffer = false;
+	}
+	
+	public boolean isStencilBufferUsed() {
+		return this.useStencilBuffer;
+	}
+	
+	public void useStencilBuffer(boolean stencil) {
+		this.useStencilBuffer = stencil;
 	}
 	
 	public TextureData getDepthBinding() {
 	private static void validateColorFormat(TextureData data) throws IllegalArgumentException {
 		if (data != null && data.getDataFormat() == TextureFormat.DEPTH)
 			throw new IllegalArgumentException("Color attachment must have a non-depth format, not: " + data.getDataFormat());
+		if (data != null && !(data.getDataFormat().getNumComponents() == 4 || data.getDataFormat().getNumComponents() == 3))
+			throw new IllegalArgumentException("Color attachment must have a 3 or 4 component format");
 		if (data != null && (data.getDataFormat().isClientCompressed() || data.getDataCompression() != TextureCompression.NONE))
 			throw new IllegalArgumentException("Color attachment can't be compressed: " + data.getDataFormat() + " " + data.getDataCompression());
 	}
 	
 	public void setColorBinding(Texture2D data, int drawBuffer) throws IllegalArgumentException {
-		validateColorFormat(data);
-		this.ensureExistence(drawBuffer);
-		setAttachment(data, this.colorArray[drawBuffer]);
+		if (data == null || !this.isTexturePresent(data)) {
+			validateColorFormat(data);
+			this.ensureExistence(drawBuffer);
+			setAttachment(data, this.colorArray[drawBuffer]);
+		}
 	}
 	
 	public void setColorBinding(TextureCubeMap data, Face face, int drawBuffer) throws IllegalArgumentException {
-		validateColorFormat(data);
-		this.ensureExistence(drawBuffer);
-		setAttachment(data, face, this.colorArray[drawBuffer]);
+		if (data == null || !this.isTexturePresent(data)) {
+			validateColorFormat(data);
+			this.ensureExistence(drawBuffer);
+			setAttachment(data, face, this.colorArray[drawBuffer]);
+		}
 	}
 	
 	public void setColorBinding(Texture3D data, int slice, int drawBuffer) throws IllegalArgumentException {
-		validateColorFormat(data);
-		this.ensureExistence(drawBuffer);
-		setAttachment(data, slice, this.colorArray[drawBuffer]);
+		if (data == null || !this.isTexturePresent(data)) {
+			validateColorFormat(data);
+			this.ensureExistence(drawBuffer);
+			setAttachment(data, slice, this.colorArray[drawBuffer]);
+		}
+	}
+	
+	private boolean isTexturePresent(TextureData data) {
+		if (data == null)
+			return false;
+		for (int i = 0; i < this.colorArray.length; i++) {
+			if (this.colorArray[i] != null && this.colorArray[i].data == data)
+				return true;
+		}
+		return false;
 	}
 	
 	public int getWidth() {

File src/com/ferox/core/util/io/IOManager.java

 		BufferData d = new BufferData(null, BufferData.DataType.FLOAT, 3);
 		VertexArray a = new VertexArray(d, 3);
 		Geometry g = new Geometry(a, null, Geometry.PolygonType.TRIANGLES);
-		GLSLShaderObject o = new GLSLShaderObject(null, GLSLShaderObject.GLSLType.VERTEX);
+		GLSLShaderObject o = new GLSLShaderObject(new String[] {""}, GLSLShaderObject.GLSLType.VERTEX);
 		
 		IOManager.registerInstantiator(d);
 		IOManager.registerInstantiator(a);

File src/com/ferox/impl/jsr231/JOGLPassiveRenderContext.java

 			this.gl.glEnable(GL.GL_MULTISAMPLE);
 		this.gl.glEnable(GL.GL_SCISSOR_TEST);
 		this.gl.glEnable(GL.GL_RESCALE_NORMAL);
-		this.gl.glShadeModel(GL.GL_FLAT);
+		this.gl.glShadeModel(GL.GL_SMOOTH);
 		
 		int red, green, blue, alpha, stencil, depth;
 		this.gl.glGetIntegerv(GL.GL_RED_BITS, t, 0);

File src/com/ferox/impl/jsr231/peers/CopyTexturePeer.java

 	}
 
 	private static void copyPixels(TextureData data, int slice, Face face, int width, int height, JOGLRenderContext context) {
-		//FIXME: center region onto the screen if the texture is smaller than the context dims.
-		Block region = new Block(0, 0, slice, Math.min(width, context.getContextWidth()), Math.min(height, context.getContextHeight()), 1);
+		width = Math.min(width, context.getContextWidth());
+		height = Math.min(height, context.getContextHeight());
+		int x = (context.getContextWidth() - width) / 2;
+		int y = (context.getContextHeight() - height) / 2;
+		
+		Block region = new Block(0, 0, slice, width, height, 1);
 		if (data.getTarget() == TextureTarget.TEX2D)
-			context.copyFramePixels((Texture2D)data, region, 0, 0, 0);
+			context.copyFramePixels((Texture2D)data, region, 0, x, y);
 		else if (data.getTarget() == TextureTarget.TEX3D)
-			context.copyFramePixels((Texture3D)data, region, 0, 0, 0);
+			context.copyFramePixels((Texture3D)data, region, 0, x, y);
 		else
-			context.copyFramePixels((TextureCubeMap)data, region, face, 0, 0, 0);
+			context.copyFramePixels((TextureCubeMap)data, region, face, 0, x, y);
 	}
 	
 	public void prepare(RenderToTexturePass pass, JOGLRenderContext context) {

File src/com/ferox/impl/jsr231/peers/FBOPeer.java

 package com.ferox.impl.jsr231.peers;
 
+import java.util.HashMap;
+
+import javax.media.opengl.GL;
+
+import com.ferox.core.renderer.RenderManager;
 import com.ferox.core.renderer.RenderToTexturePass;
+import com.ferox.core.states.NumericUnit;
+import com.ferox.core.states.atoms.TextureData;
+import com.ferox.core.states.atoms.TextureCubeMap.Face;
+import com.ferox.core.util.FeroxException;
 import com.ferox.impl.jsr231.JOGLRenderContext;
 import com.ferox.impl.jsr231.peers.JOGLRenderToTexturePassPeer.RTTPeer;
 
 public class FBOPeer implements RTTPeer {
-
+	private static class TextureAttachment {
+		int slice;
+		Face face;
+		int texID;
+		int target;
+		TextureRecord prev;
+	}
+	
+	private static class RenderBuffer {
+		int id;
+		int width, height;
+	}
+	
+	private static class FBOPassImpl {
+		int fboId;
+		int[] colorBuffers;
+		RenderBuffer stencil;
+		RenderBuffer depth;
+		TextureAttachment depthT;
+		TextureAttachment[] colors;
+		int contextVersion;
+	}
+	
+	private HashMap<RenderToTexturePass, FBOPassImpl> fboRecord;
+	
+	public FBOPeer() {
+		this.fboRecord = new HashMap<RenderToTexturePass, FBOPassImpl>();
+	}
+	
 	public void finish(RenderToTexturePass pass, JOGLRenderContext context) {
-		// TODO Auto-generated method stub
-		
+		FBOPassImpl fbo = this.fboRecord.get(pass);
+		if (fbo != null)
+			restoreFBO(fbo, context);
 	}
 
-	public void prepare(RenderToTexturePass pass, JOGLRenderContext context) {
-		// TODO Auto-generated method stub
+	public void prepare(RenderToTexturePass pass, JOGLRenderContext context) throws FeroxException {
+		FBOPassImpl fbo = this.fboRecord.get(pass);
+		if (fbo == null || context.getContextVersion() > fbo.contextVersion) {
+			fbo = createFBO(context.getGL(), context.getContextVersion());
+			this.fboRecord.put(pass, fbo);
+		}
+		applyFBO(pass, fbo, context);
+	}
+	
+	private static void restoreFBO(FBOPassImpl fbo, JOGLRenderContext context) {
+		context.getGL().glBindFramebufferEXT(GL.GL_FRAMEBUFFER_EXT, 0);
+	}
+	
+	private static void applyFBO(RenderToTexturePass pass, FBOPassImpl fbo, JOGLRenderContext context) throws FeroxException {
+		int width = pass.getWidth();
+		int height = pass.getHeight();
+		GL gl = context.getGL();
+		gl.glBindFramebufferEXT(GL.GL_FRAMEBUFFER_EXT, fbo.fboId);
+		applyDepthAttachment(pass, fbo, width, height, context);
+		applyStencilAttachment(pass, fbo, width, height, context);
+		applyColorAttachments(pass, fbo, context);
 		
+		int error = gl.glCheckFramebufferStatusEXT(GL.GL_FRAMEBUFFER_EXT);
+		if (error != GL.GL_FRAMEBUFFER_COMPLETE_EXT) {
+			String msg = "FBO failed completion test, unable to render";
+			switch(error) {
+			case GL.GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT: msg = "FBO attachments aren't complete"; break;
+			case GL.GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT: msg = "FBO needs at least one attachment"; break;
+			case GL.GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT: msg = "FBO draw buffers improperly enabled"; break;
+			case GL.GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT: msg = "FBO read buffer improperly enabled"; break;
+			case GL.GL_FRAMEBUFFER_UNSUPPORTED_EXT: msg = "Texture attachment formats aren't supported by this vendor"; break;
+			}
+			throw new FeroxException(msg);
+		}
+	}
+	
+	private static void applyColorAttachments(RenderToTexturePass pass, FBOPassImpl fbo, JOGLRenderContext context) {
+		GL gl = context.getGL();
+		TextureData color;
+		Face face;
+		int slice;
+		TextureAttachment tex;
+		int bCount = 0;
+		for (int i = 0; i < fbo.colors.length; i++) {
+			color = pass.getColorBinding(i);
+			tex = fbo.colors[i];
+			if (color == null) {
+				if (tex != null) {
+					attachTexture(0, tex.target, tex.face, tex.slice, GL.GL_COLOR_ATTACHMENT0_EXT + i, gl);
+					fbo.colors[i] = null;
+				}
+			} else {
+				face = pass.getColorBindingFace(i);
+				slice = pass.getColorBindingSlice(i);
+				TextureRecord r = getRecord(color, context);
+				if (tex == null) {
+					tex = new TextureAttachment();
+					updateTextureAttachment(tex, r, face, slice, GL.GL_COLOR_ATTACHMENT0_EXT + i, context);
+					fbo.colors[i] = tex;
+				} else if (tex.prev != r || r.target != tex.target || tex.slice != slice || tex.face != face) {
+					if (r.target != tex.target)
+						attachTexture(0, tex.target, tex.face, tex.slice, GL.GL_COLOR_ATTACHMENT0_EXT + i, gl);
+					updateTextureAttachment(tex, r, face, slice, GL.GL_COLOR_ATTACHMENT0_EXT + i, context);
+				}
+				
+				fbo.colorBuffers[bCount] = GL.GL_COLOR_ATTACHMENT0_EXT + i;
+				bCount++;
+			}
+		}
+		if (bCount == 0) {
+			gl.glDrawBuffer(GL.GL_NONE);
+			gl.glReadBuffer(GL.GL_NONE);
+		} else {
+			gl.glDrawBuffers(bCount, fbo.colorBuffers, 0);
+			gl.glReadBuffer(fbo.colorBuffers[0]);
+		}
+	}
+	
+	private static void updateTextureAttachment(TextureAttachment tex, TextureRecord data, Face face, int slice, int attachment, JOGLRenderContext context) {
+		tex.face = face;
+		tex.slice = slice;
+		
+		tex.prev = data;
+		tex.texID = data.texID;
+		tex.target = data.target;
+		attachTexture(tex.texID, tex.target, face, slice, attachment, context.getGL());
+	}
+	
+	private static void updateRenderBufferAttachment(RenderBuffer rb, int width, int height, int attachment, int type, GL gl) {
+		boolean attach = false;
+		if (rb.id <= 0) {
+			attach = true;
+			rb.id = createRenderBuffer(gl);
+		}
+		rb.width = width;
+		rb.height = height;
+		bindRenderBuffer(rb.id, gl);
+		renderBufferStorage(width, height, type, gl);
+		if (attach)
+			attachRenderBuffer(rb.id, attachment, gl);
+		bindRenderBuffer(0, gl);
+	}
+	
+	private static void applyStencilAttachment(RenderToTexturePass pass, FBOPassImpl fbo, int width, int height, JOGLRenderContext context) {
+		GL gl = context.getGL();
+		if (fbo.stencil == null) {
+			if (pass.isStencilBufferUsed()) {
+				fbo.stencil = new RenderBuffer();
+				updateRenderBufferAttachment(fbo.stencil, width, height, GL.GL_STENCIL_ATTACHMENT_EXT, GL.GL_STENCIL_INDEX, gl);
+			}
+		} else {
+			if (pass.isStencilBufferUsed()) {
+				if (fbo.stencil.width != width || fbo.stencil.height != height) {
+					updateRenderBufferAttachment(fbo.stencil, width, height, GL.GL_STENCIL_ATTACHMENT_EXT, GL.GL_STENCIL_INDEX, gl);
+				}
+			} else {
+				attachRenderBuffer(0, GL.GL_STENCIL_ATTACHMENT_EXT, gl);
+				deleteRenderBuffer(fbo.stencil.id, gl);
+				fbo.stencil = null;
+			}
+		}
+	}
+	
+	private static void applyDepthAttachment(RenderToTexturePass pass, FBOPassImpl fbo, int width, int height, JOGLRenderContext context) {
+		TextureData depth = pass.getDepthBinding();
+		GL gl = context.getGL();
+		if (depth == null) {
+			if (fbo.depth == null) {
+				if (fbo.depthT != null) {
+					attachTexture(0, fbo.depthT.target, fbo.depthT.face, fbo.depthT.slice, GL.GL_DEPTH_ATTACHMENT_EXT, gl);
+					fbo.depthT = null;
+				}
+				fbo.depth = new RenderBuffer();
+				updateRenderBufferAttachment(fbo.depth, width, height, GL.GL_DEPTH_ATTACHMENT_EXT, GL.GL_DEPTH_COMPONENT, gl);
+			} else if (fbo.depth.width != width || fbo.depth.height != height) {
+				updateRenderBufferAttachment(fbo.depth, width, height, GL.GL_DEPTH_ATTACHMENT_EXT, GL.GL_DEPTH_COMPONENT, gl);
+			}
+		} else {
+			TextureRecord r = getRecord(depth, context);
+			if (fbo.depthT == null) {
+				if (fbo.depth != null) {
+					attachRenderBuffer(0, GL.GL_DEPTH_ATTACHMENT_EXT, gl);
+					deleteRenderBuffer(fbo.depth.id, gl);
+					fbo.depth = null;
+				}
+				fbo.depthT = new TextureAttachment();
+				updateTextureAttachment(fbo.depthT, r, pass.getDepthBindingFace(), pass.getDepthBindingSlice(), GL.GL_DEPTH_ATTACHMENT_EXT, context);
+			} else {
+				Face face = pass.getDepthBindingFace();
+				int slice = pass.getDepthBindingSlice();
+				if (fbo.depthT.prev != r || r.target != fbo.depthT.target || fbo.depthT.slice != slice || fbo.depthT.face != face) {
+					if (r.target != fbo.depthT.target)
+						attachTexture(0, fbo.depthT.target, fbo.depthT.face, fbo.depthT.slice, GL.GL_DEPTH_ATTACHMENT_EXT, gl);
+					updateTextureAttachment(fbo.depthT, r, face, slice, GL.GL_DEPTH_ATTACHMENT_EXT, context);
+				}
+			}
+		}
+	}
+	
+	private static TextureRecord getRecord(TextureData data, JOGLRenderContext context) {
+		RenderManager rm = context.getRenderManager();
+		NumericUnit u = NumericUnit.get(0);
+		TextureData prev = (TextureData)context.getActiveStateAtom(TextureData.class, u);
+		data.applyState(rm, u);
+		TextureRecord r = (TextureRecord)data.getStateRecord(rm);
+		if (prev != null)
+			prev.applyState(rm, u);
+		else
+			data.restoreState(rm, u);
+		return r;
+	}
+	
+	private static void attachTexture(int id, int target, Face face, int slice, int attachment, GL gl) {
+		switch(target) {
+		case GL.GL_TEXTURE_2D: case GL.GL_TEXTURE_RECTANGLE_ARB:
+			gl.glFramebufferTexture2DEXT(GL.GL_FRAMEBUFFER_EXT, attachment, target, id, 0);
+			break;
+		case GL.GL_TEXTURE_3D:
+			gl.glFramebufferTexture3DEXT(GL.GL_FRAMEBUFFER_EXT, attachment, GL.GL_TEXTURE_3D, id, 0, slice);
+			break;
+		case GL.GL_TEXTURE_CUBE_MAP:
+			int cubetarget = 0;
+			switch(face) {
+			case PX: cubetarget = GL.GL_TEXTURE_CUBE_MAP_POSITIVE_X; break;
+			case NX: cubetarget = GL.GL_TEXTURE_CUBE_MAP_NEGATIVE_X; break;
+			case PY: cubetarget = GL.GL_TEXTURE_CUBE_MAP_POSITIVE_Y; break;
+			case NY: cubetarget = GL.GL_TEXTURE_CUBE_MAP_NEGATIVE_Y; break;
+			case PZ: cubetarget = GL.GL_TEXTURE_CUBE_MAP_POSITIVE_Z; break;
+			case NZ: cubetarget = GL.GL_TEXTURE_CUBE_MAP_NEGATIVE_Z; break;
+			}
+			gl.glFramebufferTexture2DEXT(GL.GL_FRAMEBUFFER_EXT, attachment, cubetarget, id, 0);
+			break;
+		}
 	}
 
+	private static int createRenderBuffer(GL gl) {
+		int[] id = new int[1];
+		gl.glGenRenderbuffersEXT(1, id, 0);
+		return id[0];
+	}
+	
+	private static void deleteRenderBuffer(int rid, GL gl) {
+		gl.glDeleteRenderbuffersEXT(1, new int[] {rid}, 0);
+	}
+	
+	private static void attachRenderBuffer(int rid, int attach, GL gl) {
+		gl.glFramebufferRenderbufferEXT(GL.GL_FRAMEBUFFER_EXT, attach, GL.GL_RENDERBUFFER_EXT, rid);
+	}
+	
+	private static void bindRenderBuffer(int rid, GL gl) {
+		gl.glBindRenderbufferEXT(GL.GL_RENDERBUFFER_EXT, rid);
+	}
+	
+	private static void renderBufferStorage(int width, int height, int type, GL gl) {
+		gl.glRenderbufferStorageEXT(GL.GL_RENDERBUFFER_EXT, type, width, height);
+	}
+	
+	private static FBOPassImpl createFBO(GL gl, int version) {
+		int[] id = new int[1];
+		gl.glGenFramebuffersEXT(1, id, 0);
+		
+		FBOPassImpl fbo = new FBOPassImpl();
+		fbo.fboId = id[0];
+		
+		fbo.depth = null;
+		fbo.depthT = null;
+		fbo.stencil = null;
+		fbo.colors = new TextureAttachment[RenderToTexturePass.getMaxColorAttachments()];
+		fbo.colorBuffers = new int[fbo.colors.length];
+		fbo.contextVersion = version;
+		return fbo;
+	}
 }

File src/com/ferox/impl/jsr231/peers/JOGLRenderToTexturePassPeer.java

 	public void prepareRenderPass(RenderPass pass) {
 		if (this.peer == null) {
 			if (RenderManager.getSystemCapabilities().isFBOSupported())
-				this.peer = new CopyTexturePeer();
+				this.peer = new FBOPeer();
 			else
 				this.peer = new CopyTexturePeer();
 		}

File src/com/ferox/test/LightTest.java

 import java.awt.Component;
 import java.awt.event.KeyEvent;
 import java.io.File;
+import java.io.IOException;
 import java.nio.FloatBuffer;
 import java.nio.IntBuffer;
 
 import com.ferox.core.util.InputManager;
 import com.ferox.core.util.TextureResourceManager;
 import com.ferox.core.util.TimeSmoother;
+import com.ferox.core.util.io.IOManager;
 import com.ferox.impl.jsr231.JOGLPassiveRenderContext;
 import com.sun.opengl.util.BufferUtil;
 
 
 		Geometry geom = null;
 		Geometry geom2 = null;
-		/*try {
-			now = System.currentTimeMillis();
-			geom = (Geometry)com.ferox.util.io.IOManager.read(new File("dragon_4.ido2"));
-			System.out.println("total read/write time: " + (System.currentTimeMillis() - now));
+		try {
+			geom = (Geometry)IOManager.read(new File("data/models/dragon_2.ido2"));
 			System.out.println("geom verts: " + geom.getVertices().getNumElements() + " geom indices: " + geom.getIndices().getNumElements() + " polys: " + geom.getPolygonCount());
 		} catch(IOException ioe) {
 			System.out.println(ioe);
 			System.exit(1);
-		}*/
-		/*try {
-			now = System.currentTimeMillis();
-			geom2 = (Geometry)com.ferox.util.io.IOManager.read(new File("dragon_big.ido2"));
-			System.out.println("total read/write time: " + (System.currentTimeMillis() - now));
+		}
+		try {
+			geom2 = (Geometry)IOManager.read(new File("data/models/dragon_3.ido2"));
 			System.out.println("geom verts: " + geom.getVertices().getNumElements() + " geom indices: " + geom.getIndices().getNumElements() + " polys: " + geom.getPolygonCount());
 		} catch(IOException ioe) {
 			System.out.println(ioe);
 			System.exit(1);
-		}*/ 
-		geom = buildCube();
-		geom2 = geom;
+		}
+		//geom = buildCube();
+		//geom2 = geom;
 		
 		a1.addStateManager(geom);
 		a2.addStateManager(geom2);
 		
-		for (int i = 0; i < 10000; i++) {
+		for (int i = 0; i < 100; i++) {
 			SpatialLeaf spat_atom;
 			StateLeaf stat_atom;
 			double r = Math.random();

File src/com/ferox/test/RTTTest.java

 	}
 	
 	private RTTTest() {
-		this.manager = new RenderManager(new JOGLPassiveRenderContext(new DisplayOptions()));
+		DisplayOptions op = new DisplayOptions();
+		//op.setNumMultiSamples(4);
+		this.manager = new RenderManager(new JOGLPassiveRenderContext(op));
 		
 		this.window = new JFrame("GLSLTest");
 		this.window.add((Component)((OnscreenRenderSurface)this.manager.getRenderingSurface()).getRenderSurface());
 	private SpatialTree buildRTTScene(RenderManager manager) {
 		Component c = (Component)((OnscreenRenderSurface)this.manager.getRenderingSurface()).getRenderSurface();
 		int min = Math.min(c.getWidth(), c.getHeight());
-		this.rttStore = new Texture2D(null, min, min, TextureType.UNSIGNED_BYTE, TextureFormat.RGB, TexClamp.CLAMP, MinFilter.LINEAR, MagFilter.LINEAR);
+		this.rttStore = new Texture2D(null, min, min, TextureType.UNCLAMPED_FLOAT, TextureFormat.RGB, TexClamp.CLAMP, MinFilter.LINEAR, MagFilter.LINEAR);
 		
 		SpatialBranch root = new SpatialBranch(null, 1);
 		Geometry cube = this.buildCube(1f);