/******************************************************************************* * Copyright 2012 Geoscience Australia * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package au.gov.ga.earthsci.worldwind.common.render; import gov.nasa.worldwind.util.OGLStackHandler; import java.awt.Dimension; import javax.media.opengl.GL2; import au.gov.ga.earthsci.worldwind.common.util.Validate; /** * Helper class for the creation and binding of an OpenGL Frame Buffer Object. * * @author Michael de Hoog (michael.dehoog@ga.gov.au) * @author James Navin (james.navin@ga.gov.au) */ public class FrameBuffer { private final FrameBufferTexture[] textures; private final FrameBufferDepthBuffer depth = new FrameBufferDepthBuffer(); private int frameBufferId = 0; private Dimension currentDimensions = null; /** * Create a new frame buffer. */ public FrameBuffer() { this(1); } /** * Create a new frame buffer. * * @param textureCount * Number of textures to bind to this frame buffer */ public FrameBuffer(int textureCount) { this(textureCount, false); } /** * Create a new frame buffer. * * @param textureCount * Number of textures to bind to this frame buffer * @param depthAsTexture * Use a texture for the depth buffer */ public FrameBuffer(int textureCount, boolean depthAsTexture) { if (textureCount < 1) { throw new IllegalArgumentException("Must be at least one texture bound to the frame buffer"); } textures = new FrameBufferTexture[textureCount]; for (int i = 0; i < textureCount; i++) { textures[i] = new FrameBufferTexture(); } depth.setTexture(depthAsTexture); } /** * Create a frame buffer, and its texture and depth buffer (but don't bind) * * @param gl * GL context * @param dimensions * Frame buffer dimensions */ public void create(GL2 gl, Dimension dimensions) { //generate a texture, depth buffer, and frame buffer frameBufferId = generateFrameBuffer(gl); depth.create(gl, dimensions); for (FrameBufferTexture texture : textures) { texture.create(gl, dimensions); } //bind the frame buffer bind(gl); //bind the color and depth attachments to the frame buffer int colorAttachment = GL2.GL_COLOR_ATTACHMENT0; for (FrameBufferTexture texture : textures) { gl.glFramebufferTexture2D(GL2.GL_FRAMEBUFFER, colorAttachment++, texture.getTarget(), texture.getId(), 0); } if (depth.isTexture()) { gl.glFramebufferTexture2D(GL2.GL_FRAMEBUFFER, GL2.GL_DEPTH_ATTACHMENT, GL2.GL_TEXTURE_2D, depth.getId(), 0); } else { gl.glFramebufferRenderbuffer(GL2.GL_FRAMEBUFFER, GL2.GL_DEPTH_ATTACHMENT, GL2.GL_RENDERBUFFER, depth.getId()); } //check to see if the frame buffer is supported and complete checkFrameBuffer(gl); //unbind the frame buffer (bound later) unbind(gl); currentDimensions = dimensions; } /** * Ensure this frame buffer has the correct dimensions. If the frame buffer * has not yet been created, it is created now. If it has already been * created, and the dimensions are different, it is deleted and recreated * with the new dimensions. * * @param gl * @param dimensions */ public void resize(GL2 gl, Dimension dimensions) { Validate.notNull(dimensions, "Dimensions cannot be null"); if (isCreated() && dimensions.equals(currentDimensions)) { return; //already the correct dimensions } delete(gl); create(gl, dimensions); } /** * Bind the frame buffer */ public void bind(GL2 gl) { FrameBufferStack.push(gl, frameBufferId); } /** * Unbind the frame buffer */ public void unbind(GL2 gl) { FrameBufferStack.pop(gl); } /** * Performs necessary cleanup to remove the frame buffer */ public void delete(GL2 gl) { if (isCreated()) { gl.glDeleteFramebuffers(1, new int[] { frameBufferId }, 0); frameBufferId = 0; } for (FrameBufferTexture texture : textures) { texture.delete(gl); } depth.delete(gl); currentDimensions = null; } /** * Delete if created. * * @param gl * @see FrameBuffer#delete(GL2) */ public void deleteIfCreated(GL2 gl) { if (isCreated()) { delete(gl); } } public boolean isCreated() { return frameBufferId > 0; } public FrameBufferTexture getTexture() { return textures[0]; } public FrameBufferTexture[] getTextures() { return textures; } public FrameBufferDepthBuffer getDepth() { return depth; } public Dimension getDimensions() { return currentDimensions; } /** * @return The ID of the generated frame buffer object */ protected int generateFrameBuffer(GL2 gl) { int[] frameBuffers = new int[1]; gl.glGenFramebuffers(1, frameBuffers, 0); if (frameBuffers[0] <= 0) { throw new IllegalStateException("Error generating frame buffer"); } return frameBuffers[0]; } /** * Check that the frame buffer is complete; if not, throws an exception */ protected void checkFrameBuffer(GL2 gl) { int status = gl.glCheckFramebufferStatus(GL2.GL_FRAMEBUFFER); if (status == GL2.GL_FRAMEBUFFER_UNSUPPORTED) { throw new IllegalStateException("Frame buffer unsupported, or parameters incorrect"); } else if (status != GL2.GL_FRAMEBUFFER_COMPLETE) { throw new IllegalStateException("Frame buffer incomplete"); } } /** * Draw a texture on a quad, covering the entire viewport */ public static void renderTexturedQuad(GL2 gl, int... textureIds) { renderTexturedQuadUsingTarget(gl, GL2.GL_TEXTURE_2D, textureIds); } /** * Draw a texture on a quad, covering the entire viewport */ public static void renderTexturedQuadUsingTarget(GL2 gl, int target, int... textureIds) { OGLStackHandler oglsh = new OGLStackHandler(); oglsh.pushModelviewIdentity(gl); oglsh.pushProjectionIdentity(gl); oglsh.pushAttrib(gl, GL2.GL_ENABLE_BIT); try { gl.glEnable(target); for (int i = 0; i < textureIds.length; i++) { gl.glActiveTexture(GL2.GL_TEXTURE0 + i); gl.glBindTexture(target, textureIds[i]); } gl.glBegin(GL2.GL_QUADS); { gl.glTexCoord2f(0, 0); gl.glVertex3i(-1, -1, -1); gl.glTexCoord2f(1, 0); gl.glVertex3i(1, -1, -1); gl.glTexCoord2f(1, 1); gl.glVertex3i(1, 1, -1); gl.glTexCoord2f(0, 1); gl.glVertex3i(-1, 1, -1); } gl.glEnd(); } finally { oglsh.pop(gl); } } }