JOGL says FBOs are unsupported

First, I got the JOGL plugin for NetBeans, started a stock project, and put the FBO code into the following class, which I kind “Javaized” from here

http://www.songho.ca/opengl/gl_fbo.html

Now, I can run the example at that web page, under the OpenGL Capabilities that Netbeans says I have is GL_ARB_framebuffer_object, and I have a NVIDIA 8800 GT that I figured would be high end enough.

But the following line prints 36061, which according to the documents is GL_FRAMEBUFFER_UNSUPPORTED_EXT.

System.out.println(gl.glCheckFramebufferStatusEXT(gl.GL_FRAMEBUFFER_EXT));

Have I done anything wrong here?

Class with my FBO code:


package org.yourorghere;

import java.nio.IntBuffer;
import javax.media.opengl.GL;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLEventListener;
import javax.media.opengl.glu.GLU;

public class GLRenderer implements GLEventListener {
    private final int texture = 0, frame = 1;
    private IntBuffer buffer;

    public void init(GLAutoDrawable drawable) {

        GL gl = drawable.getGL();
        System.err.println("INIT GL IS: " + gl.getClass().getName());

        gl.setSwapInterval(1);

        //fbo code
        gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
        gl.glShadeModel(GL.GL_SMOOTH);

        buffer = IntBuffer.allocate(2);

        buffer.position(frame);
        gl.glGenFramebuffersEXT(1, buffer);
        gl.glBindFramebufferEXT(gl.GL_FRAMEBUFFER_EXT, buffer.get(frame));

        gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
        gl.glShadeModel(GL.GL_SMOOTH);

        buffer.position(texture);
        gl.glGenTextures(1, buffer);
        gl.glBindTexture( gl.GL_TEXTURE_2D, buffer.get(texture));
        gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, gl.GL_RGBA, 512, 512, 0, gl.GL_RGBA, gl.GL_INT, null);

        gl.glFramebufferTexture2DEXT(gl.GL_FRAMEBUFFER_EXT, gl.GL_COLOR_ATTACHMENT0_EXT, gl.GL_TEXTURE_2D, buffer.get(texture), 0);

        System.out.println(gl.glCheckFramebufferStatusEXT(gl.GL_FRAMEBUFFER_EXT));

        gl.glMatrixMode(gl.GL_PROJECTION);
	gl.glLoadIdentity();

	gl.glOrtho(0, 512, 512, 0, -1, 1);

	gl.glMatrixMode(gl.GL_MODELVIEW);
	gl.glLoadIdentity();

        gl.glBegin(gl.GL_TRIANGLES);
            gl.glColor3f(1, 0, 0);
            gl.glVertex2f(256, 0);
            gl.glColor3f(0, 1, 0);
            gl.glVertex2f(512, 512);
            gl.glColor3f(0, 0, 1);
            gl.glVertex2f(0, 512);
        gl.glEnd();

        gl.glBindFramebufferEXT(gl.GL_FRAMEBUFFER_EXT, 0);

        gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
        gl.glShadeModel(GL.GL_SMOOTH);
    }

    public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) {
        GL gl = drawable.getGL();
        GLU glu = new GLU();

        if (height <= 0) {
        
            height = 1;
        }
        final float h = (float) width / (float) height;
        gl.glViewport(0, 0, width, height);
        gl.glMatrixMode(GL.GL_PROJECTION);
        gl.glLoadIdentity();
        glu.gluPerspective(45.0f, h, 1.0, 20.0);
        gl.glMatrixMode(GL.GL_MODELVIEW);
        gl.glLoadIdentity();
    }

    public void display(GLAutoDrawable drawable) {
        GL gl = drawable.getGL();

        gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
        gl.glLoadIdentity();

        gl.glTranslatef(-1.5f, 0.0f, -6.0f);

        gl.glBegin(GL.GL_TRIANGLES);
            gl.glColor3f(1.0f, 0.0f, 0.0f);
            gl.glVertex3f(0.0f, 1.0f, 0.0f);
            gl.glColor3f(0.0f, 1.0f, 0.0f);
            gl.glVertex3f(-1.0f, -1.0f, 0.0f);
            gl.glColor3f(0.0f, 0.0f, 1.0f);
            gl.glVertex3f(1.0f, -1.0f, 0.0f);
        gl.glEnd();

        gl.glTranslatef(3.0f, 0.0f, 0.0f);
        gl.glBegin(GL.GL_QUADS);
            gl.glColor3f(0.5f, 0.5f, 1.0f);
            gl.glVertex3f(-1.0f, 1.0f, 0.0f);
            gl.glVertex3f(1.0f, 1.0f, 0.0f);
            gl.glVertex3f(1.0f, -1.0f, 0.0f);
            gl.glVertex3f(-1.0f, -1.0f, 0.0f);
        gl.glEnd();

        gl.glFlush();
    }

    public void displayChanged(GLAutoDrawable drawable, boolean modeChanged, boolean deviceChanged) {
    }
}


And if you need it, the main class that NetBeans made:


package org.yourorghere;

import com.sun.opengl.util.Animator;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.media.opengl.GLCanvas;
import javax.media.opengl.GLCapabilities;
import javax.swing.GroupLayout;
import javax.swing.GroupLayout.Alignment;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPopupMenu;
import javax.swing.LayoutStyle.ComponentPlacement;
import javax.swing.UIManager;
import javax.swing.WindowConstants;

public class FBOtest extends JFrame {

    static {
        JPopupMenu.setDefaultLightWeightPopupEnabled(false);
    }
    
    private Animator animator;

    public FBOtest() {
        initComponents();
        setTitle("Simple JOGL Application");

        canvas.addGLEventListener(new GLRenderer());
        animator = new Animator(canvas);

        canvas.setMinimumSize(new Dimension());         
        
        this.addWindowListener(new WindowAdapter() {

            @Override
            public void windowClosing(WindowEvent e) {
                new Thread(new Runnable() {

                    public void run() {
                        animator.stop();
                        System.exit(0);
                    }
                }).start();
            }
        });
    }

    @Override
    public void setVisible(boolean show){
        if(!show)
            animator.stop();
        super.setVisible(show);
        if(!show)
            animator.start();
    }

    @SuppressWarnings("unchecked")
    private void initComponents() {
        JLabel label = new JLabel();
        canvas = new GLCanvas(createGLCapabilites());

        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

        label.setText("Below you see a GLCanvas");

        GroupLayout layout = new GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(Alignment.LEADING)
                    .addComponent(canvas, GroupLayout.DEFAULT_SIZE, 380, Short.MAX_VALUE)
                    .addComponent(label))
                .addContainerGap())
        
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addComponent(label)
                .addPreferredGap(ComponentPlacement.RELATED)
                .addComponent(canvas, GroupLayout.DEFAULT_SIZE, 255, Short.MAX_VALUE)
                .addContainerGap())
        
        );

        pack();
    }

    private GLCapabilities createGLCapabilites() {
        
        GLCapabilities capabilities = new GLCapabilities();
        capabilities.setHardwareAccelerated(true);

        capabilities.setNumSamples(2);
        capabilities.setSampleBuffers(true);
        
        return capabilities;
    }
    
    public static void main(String args[]) {
        EventQueue.invokeLater(new Runnable() {
            public void run() {

                try{
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                }catch(Exception ex) {
                    Logger.getLogger(getClass().getName()).log(Level.INFO, "can not enable system look and feel", ex);
                }

                FBOtest frame = new FBOtest();
                frame.setVisible(true);
            }
        });
    }

    private GLCanvas canvas;

}


That error code doesn’t indicate that FBOs are unsupported in general, but rather, the FBO setup that you’re doing in your code is not supported.
Here’s your problem:

gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, gl.GL_RGBA, 512, 512, 0, gl.GL_RGBA, gl.GL_INT, null);

You’re using GL_RGBA as internalFormat, and GL_INT as datatype, both of these are incorrect, GL_RGBA cannot be used for internalFormat and GL_INT implies signed integer per component. If you want standard 32bit color then you should use GL_RGBA8 for the internalFormat and the datatype should be GL_UNSIGNED_BYTE.
In other words, this should work:

gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, gl.GL_RGBA8, 512, 512, 0, gl.GL_RGBA, gl.GL_UNSIGNED_BYTE, null);

I’ve made that change but I still get the same FBO status. I then tried passing a ByteBuffer instead of null to glTexImage2D but that didn’t change anything.


ByteBuffer pixels = ByteBuffer.allocate(512*512*4);
gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, gl.GL_RGBA8, 512, 512, 0, gl.GL_RGBA, gl.GL_UNSIGNED_BYTE, pixels);

Specifying GL_RGBA for the internal format is perfectly okay. It’s considered a base format and it just allows OpenGL drivers to decide which sized internal format to use. Similarly, GL_INT should be okay as well. It only specifies the datatype of the input data. OpenGL then converts that data into whatever format necessary based on the desired internal format.

GL_FRAMEBUFFER_UNSUPPORTED_EXT only shows up when, as said above, the combination of texture images and attachments are unsupported. This seems odd given that you only have one color attachment set up. I’d recommend using an explicit internal format (possibly of a floating point type: GL_RGBA23F) just so you know the opengl driver isn’t choosing something that’s invalid.

Additionally, experiment with adding a depth renderbuffer or depth texture attachment to see if that is required.

Another thing to try is to create the texture image before you create and bind the fbo. Then also make sure the texture image is unbound before you attach it to the fbo. Documentation doesn’t say anything about this, but it might cause problems with buggy drivers.

This is my latest rewrite, now I’m getting GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT, I wasn’t sure what kind of format to try so I just made the texture before the FBO and I’ve attached a depth and stencil buffer.

I’d hate to sound lazy but would anyone spare a minimal FBO example of their own?


package org.yourorghere;

import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import javax.media.opengl.GL;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLEventListener;
import javax.media.opengl.glu.GLU;


/**
 * GLRenderer.java 

 * author: Brian Paul (converted to Java by Ron Cemer and Sven Goethel) <P>
 *
 * This version is equal to Brian Paul's version 1.2 1999/10/21
 */
public class GLRenderer implements GLEventListener {
    private final int texture = 0, frame = 1, depth = 2, stencil = 3;
    private IntBuffer buffer;

    public void init(GLAutoDrawable drawable) {
        // Use debug pipeline
        // drawable.setGL(new DebugGL(drawable.getGL()));

        GL gl = drawable.getGL();
        System.err.println("INIT GL IS: " + gl.getClass().getName());

        //System.out.println(

        // Enable VSync
        gl.setSwapInterval(1);

        buffer = IntBuffer.allocate(4);

        buffer.position(texture);
        gl.glGenTextures(1, buffer);
        gl.glBindTexture( gl.GL_TEXTURE_2D, buffer.get(texture));
        ByteBuffer pixels = ByteBuffer.allocate(512*512*4);
        gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, gl.GL_RGBA8, 512, 512, 0, gl.GL_RGBA, gl.GL_UNSIGNED_BYTE, pixels);

        buffer.position(depth);
        gl.glGenRenderbuffersEXT(1, buffer);
        gl.glBindRenderbufferEXT(gl.GL_RENDERBUFFER_EXT, buffer.get(depth));
        gl.glRenderbufferStorageEXT(gl.GL_RENDERBUFFER_EXT, gl.GL_DEPTH_COMPONENT, 512, 512);

        buffer.position(stencil);
        gl.glGenRenderbuffersEXT(1, buffer);
        gl.glBindRenderbufferEXT(gl.GL_RENDERBUFFER_EXT, buffer.get(stencil));
        gl.glRenderbufferStorageEXT(gl.GL_RENDERBUFFER_EXT, gl.GL_STENCIL_INDEX, 512, 512);

        buffer.position(frame);
        gl.glGenFramebuffersEXT(1, buffer);
        gl.glBindFramebufferEXT(gl.GL_FRAMEBUFFER_EXT, buffer.get(frame));
        gl.glFramebufferTexture2DEXT(gl.GL_FRAMEBUFFER_EXT, gl.GL_COLOR_ATTACHMENT0_EXT, gl.GL_TEXTURE_2D, buffer.get(texture), 0);
        gl.glFramebufferRenderbufferEXT(gl.GL_FRAMEBUFFER_EXT, gl.GL_DEPTH_ATTACHMENT_EXT, gl.GL_RENDERBUFFER_EXT, buffer.get(depth));
        gl.glFramebufferRenderbufferEXT(gl.GL_FRAMEBUFFER_EXT, gl.GL_STENCIL_ATTACHMENT_EXT, gl.GL_RENDERBUFFER_EXT, buffer.get(stencil));

        gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
        gl.glShadeModel(GL.GL_SMOOTH);
        
        System.out.println(gl.glCheckFramebufferStatusEXT(gl.GL_FRAMEBUFFER_EXT));

        gl.glMatrixMode(gl.GL_PROJECTION);
	gl.glLoadIdentity();

	gl.glOrtho(0, 512, 512, 0, -1, 1);

	gl.glMatrixMode(gl.GL_MODELVIEW);
        gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
        gl.glLoadIdentity();

        gl.glBegin(gl.GL_TRIANGLES);
            gl.glColor3f(1, 0, 0);
            gl.glVertex2f(256, 0);
            gl.glColor3f(0, 1, 0);
            gl.glVertex2f(512, 512);
            gl.glColor3f(0, 0, 1);
            gl.glVertex2f(0, 512);
        gl.glEnd();

        gl.glBindFramebufferEXT(gl.GL_FRAMEBUFFER_EXT, 0);

        gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
        gl.glShadeModel(GL.GL_SMOOTH);
    }

    public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) {
        GL gl = drawable.getGL();
        GLU glu = new GLU();

        if (height <= 0) { // avoid a divide by zero error!
        
            height = 1;
        }
        final float h = (float) width / (float) height;
        gl.glViewport(0, 0, width, height);
        gl.glMatrixMode(GL.GL_PROJECTION);
        gl.glLoadIdentity();
        glu.gluPerspective(45.0f, h, 1.0, 20.0);
        gl.glMatrixMode(GL.GL_MODELVIEW);
        gl.glLoadIdentity();
    }

    public void display(GLAutoDrawable drawable) {
        GL gl = drawable.getGL();

        // Clear the drawing area
        gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
        // Reset the current matrix to the "identity"
        gl.glLoadIdentity();

        // Move the "drawing cursor" around
        gl.glTranslatef(-1.5f, 0.0f, -6.0f);

        // Drawing Using Triangles
        gl.glBegin(GL.GL_TRIANGLES);
            gl.glColor3f(1.0f, 0.0f, 0.0f);    // Set the current drawing color to red
            gl.glVertex3f(0.0f, 1.0f, 0.0f);   // Top
            gl.glColor3f(0.0f, 1.0f, 0.0f);    // Set the current drawing color to green
            gl.glVertex3f(-1.0f, -1.0f, 0.0f); // Bottom Left
            gl.glColor3f(0.0f, 0.0f, 1.0f);    // Set the current drawing color to blue
            gl.glVertex3f(1.0f, -1.0f, 0.0f);  // Bottom Right
        // Finished Drawing The Triangle
        gl.glEnd();

        // Move the "drawing cursor" to another position
        gl.glTranslatef(3.0f, 0.0f, 0.0f);
        // Draw A Quad
        gl.glBegin(GL.GL_QUADS);
            gl.glColor3f(0.5f, 0.5f, 1.0f);    // Set the current drawing color to light blue
            gl.glVertex3f(-1.0f, 1.0f, 0.0f);  // Top Left
            gl.glVertex3f(1.0f, 1.0f, 0.0f);   // Top Right
            gl.glVertex3f(1.0f, -1.0f, 0.0f);  // Bottom Right
            gl.glVertex3f(-1.0f, -1.0f, 0.0f); // Bottom Left
        // Done Drawing The Quad
        gl.glEnd();

        // Flush all drawing operations to the graphics card
        gl.glFlush();
    }

    public void displayChanged(GLAutoDrawable drawable, boolean modeChanged, boolean deviceChanged) {
    }
}



When I was first working out FBOs, I found that having a stencil buffer could cause problems, although for me it was having an UNSUPPORTED error. The incomplete attachment error means that there is some problem where your depth, stencil, or color buffer isn’t setup completely. I’m not sure how to fix this, but here’s my fbo handling class from my engine. It should be straightforward enough, but is missing some utilities from my engine:


package com.ferox.renderer.impl.jogl;

import javax.media.opengl.GL;
import javax.media.opengl.GL2GL3;

import com.ferox.renderer.RenderException;
import com.ferox.renderer.impl.ResourceHandle;
import com.ferox.resource.TextureCubeMap;
import com.ferox.resource.TextureImage;
import com.ferox.resource.TextureImage.TextureTarget;

/**
 * FramebufferObject is a low-level wrapper around an OpenGL fbo and can be used
 * to perform render-to-texture effects. These should not be used directly and
 * are intended to be managed by a {@link FboSurfaceDelegate}.
 * 
 * @author Michael Ludwig
 */
public class FramebufferObject {
	private final int fboId;
	private int renderBufferId;

	private final TextureTarget colorTarget;

	private int boundLayer;
	private int[] colorImageIds;

	public FramebufferObject(JoglFramework framework, int width, int height, 
				   			 TextureTarget colorTarget, TextureTarget depthTarget, 
				   			 TextureImage[] colors, TextureImage depth, 
				   			 int layer, boolean useDepthRenderBuffer) {
		JoglContext context = JoglContext.getCurrent();
		if (context == null)
			throw new RenderException("FramebufferObject's can only be constructed when there's a current context");
		if (!framework.getCapabilities().getFboSupport())
			throw new RenderException("Current hardware doesn't support the creation of fbos");

		GL2GL3 gl = context.getGL();

		this.colorTarget = colorTarget;

		int[] id = new int[1];
		gl.glGenFramebuffers(1, id, 0);
		fboId = id[0];
		gl.glBindFramebuffer(GL.GL_FRAMEBUFFER, fboId);

		if (depth != null) {
			// attach the depth texture
			int glDepthTarget = getGlTarget(depthTarget, layer);

			ResourceHandle h = framework.getResourceManager().getHandle(depth);
			attachImage(gl, glDepthTarget, h.getId(), layer, GL.GL_DEPTH_ATTACHMENT);

			renderBufferId = 0;
		} else if (useDepthRenderBuffer) {
			// make and attach the render buffer
			gl.glGenRenderbuffers(1, id, 0);
			renderBufferId = id[0];

			gl.glBindRenderbuffer(GL.GL_RENDERBUFFER, renderBufferId);
			gl.glRenderbufferStorage(GL.GL_RENDERBUFFER, GL2GL3.GL_DEPTH_COMPONENT, width, height);

			if (gl.glGetError() == GL.GL_OUT_OF_MEMORY) {
				gl.glBindRenderbuffer(GL.GL_RENDERBUFFER, 0);
				destroy();
				throw new RenderException("Error creating a new FBO, not enough memory for the depth RenderBuffer");
			} else
				gl.glBindRenderbuffer(GL.GL_RENDERBUFFER, 0);
			gl.glFramebufferRenderbuffer(GL.GL_FRAMEBUFFER, GL.GL_DEPTH_ATTACHMENT, 
										 GL.GL_RENDERBUFFER, renderBufferId);
		}

		if (colors != null && colors.length > 0) {
			// attach all of the images
			int glColorTarget = getGlTarget(colorTarget, layer);

			colorImageIds = new int[colors.length];
			ResourceHandle h;
			for (int i = 0; i < colors.length; i++) {
				h = framework.getResourceManager().getHandle(colors[i]);
				attachImage(gl, glColorTarget, h.getId(), layer, GL.GL_COLOR_ATTACHMENT0 + i);
				colorImageIds[i] = h.getId();
			}
		} else
			colorImageIds = null;

		boundLayer = layer;

		// Enable/disable the read/draw buffers to make the fbo "complete"
		gl.glReadBuffer(GL.GL_NONE);
		if (colorImageIds != null) {
			int[] drawBuffers = new int[colorImageIds.length];
			for (int i = 0; i < drawBuffers.length; i++)
				drawBuffers[i] = GL.GL_COLOR_ATTACHMENT0 + i;
			gl.glDrawBuffers(drawBuffers.length, drawBuffers, 0);
		} else
			gl.glDrawBuffer(GL.GL_NONE);

		int complete = gl.glCheckFramebufferStatus(GL.GL_FRAMEBUFFER);
		if (complete != GL.GL_FRAMEBUFFER_COMPLETE) {
			String msg = "FBO failed completion test, unable to render";
			switch (complete) {
			case GL.GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
				msg = "Fbo attachments aren't complete";
				break;
			case GL.GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
				msg = "Fbo needs at least one attachment";
				break;
			case GL2GL3.GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER:
				msg = "Fbo draw buffers improperly enabled";
				break;
			case GL2GL3.GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER:
				msg = "Fbo read buffer improperly enabled";
				break;
			case GL.GL_FRAMEBUFFER_UNSUPPORTED:
				msg = "Texture/Renderbuffer combinations aren't supported on the hardware";
				break;
			case 0:
				msg = "glCheckFramebufferStatusEXT() had an error while checking fbo status";
				break;
			}
			// clean-up and then throw an exception
			destroy();
			throw new RenderException(msg);
		}

		// restore the old binding
		gl.glBindFramebuffer(GL.GL_FRAMEBUFFER, context.getRecord().getFbo());
	}

	public void bind(int layer) {
		JoglContext context = JoglContext.getCurrent();
		GL2GL3 gl = context.getGL();
		
		// bind the fbo if needed
		context.getRecord().bindFbo(gl, fboId);

		// possibly re-attach the images (in the case of cubemaps or 3d textures)
		if (layer != boundLayer) {
			if (colorImageIds != null) {
				int target = getGlTarget(colorTarget, layer);
				for (int i = 0; i < colorImageIds.length; i++)
					attachImage(gl, target, colorImageIds[i], layer, 
								GL.GL_COLOR_ATTACHMENT0 + i);
			}
			// we don't have to re-attach depth images -> will always be 1d/2d/rect -> 1 layer only
			boundLayer = layer;
		}
	}

	public void release() {
		JoglContext context = JoglContext.getCurrent();
		context.getRecord().bindFbo(context.getGL(), 0);
	}

	public void destroy() {
		GL2GL3 gl = JoglContext.getCurrent().getGL();
		gl.glDeleteFramebuffers(1, new int[] { fboId }, 0);
		if (renderBufferId != 0)
			gl.glDeleteRenderbuffers(1, new int[] { renderBufferId }, 0);
	}

	// Get the appropriate texture target based on the layer and high-level target
	private static int getGlTarget(TextureTarget target, int layer) {
		switch (target) {
		case T_1D:
			return GL2GL3.GL_TEXTURE_1D;
		case T_2D:
			return GL2GL3.GL_TEXTURE_2D;
		case T_3D:
			return GL2GL3.GL_TEXTURE_3D;
		case T_RECT:
			return GL2GL3.GL_TEXTURE_RECTANGLE_ARB;
		case T_CUBEMAP:
			switch (layer) {
			case TextureCubeMap.PX:
				return GL.GL_TEXTURE_CUBE_MAP_POSITIVE_X;
			case TextureCubeMap.NX:
				return GL.GL_TEXTURE_CUBE_MAP_NEGATIVE_X;
			case TextureCubeMap.PY:
				return GL.GL_TEXTURE_CUBE_MAP_POSITIVE_Y;
			case TextureCubeMap.NY:
				return GL.GL_TEXTURE_CUBE_MAP_NEGATIVE_Y;
			case TextureCubeMap.PZ:
				return GL.GL_TEXTURE_CUBE_MAP_POSITIVE_Z;
			case TextureCubeMap.NZ:
				return GL.GL_TEXTURE_CUBE_MAP_NEGATIVE_Z;
			}
		}

		return -1;
	}

	// Attach the given texture image to the currently bound fbo (on target FRAMEBUFFER)
	private static void attachImage(GL2GL3 gl, int target, int id, int layer, int attachment) {
		switch (target) {
		case GL2GL3.GL_TEXTURE_1D:
			gl.glFramebufferTexture1D(GL.GL_FRAMEBUFFER, attachment, target, id, 0);
			break;
		case GL2GL3.GL_TEXTURE_3D:
			gl.glFramebufferTexture3D(GL.GL_FRAMEBUFFER, attachment, target, id, 0, layer);
			break;
		default: // 2d, rect, or a cubemap face
			gl.glFramebufferTexture2D(GL.GL_FRAMEBUFFER, attachment, target, id, 0);
		}
	}
}