import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.IntBuffer; import org.lwjgl.opengl.*; import org.lwjgl.util.glu.*; import org.lwjgl.input.Keyboard; /** * GLART_10_copyframe.java * * Copy the framebuffer to a texture. * * There may be times that you want to make a copy of the screen or part of the screen. For instance * you can generate a texture dynamically using openGL drawing commands, or * you may want to have a scene animated within another scene, like a movie playing in a larger * scene, or a reflection on a mirror. One technique to accomplish this is to render to the framebuffer, * then save the framebuffer to a texture. * Since the framebuffer contents and the texture are stored on the graphics card, * this operation is relatively fast. * * This demmo shows how to save the entire screen image to a texture, then draw that screen image back to * the framebuffer. * * Normally when rendering a scene in OpenGL you'll clear the framebuffer (glClear()) and draw the * entire scene in each call to render(). In this case I wanted to leave faint trails behind as geometry * moved, so I did not call glClear(). This may cause odd flickering at times because OpenGL does not * guarantee that the framebuffer won't change betweem renders. In fact, the framebuffer may contain * the remnants of some other graphical operration, as the graphics card optimizes resources on the fly. * The solution is to save an image of the screen at the end of each render(), annd write that image * into the framebuffer at the beginnning of each render(). This way the framebuffer contents can change * between renders and it won't matter, we'll just write back our last screen before we render anything * else. * * The functions: * * glCopyTexSubImage() * makeTextureForScreen() * frameSave() * frameDraw() * * See init() for the calls to create a texture to hold the screen image. * See render() for the calls to save and restore the screen image. * * */ public class GLART_10_copyframe { private boolean done = false; private final String windowTitle = "Save Frame to Texture"; private DisplayMode displayMode; private float rotation = 0f; private float rotation2 = 0f; private float rotationAmount = .08f; // some color values float R = 1f; float G = 1f; float B = 1f; // For copying screen image to a texture int screenTextureSize = 1024; // how large should texture be to hold screen // will hold screen image as texture int screenTextureHandle; /** * Main function just creates and runs the application. */ public static void main(String args[]) { GLART_10_copyframe app = new GLART_10_copyframe(); app.run(); } /** * Initialize the app, then sit in a render loop until done==true. */ public void run() { try { init(); while (!done) { mainloop(); render(); Display.update(); } cleanup(); } catch (Exception e) { e.printStackTrace(); System.exit(0); } } /** * Initialize the environment * @throws Exception */ private void init() throws Exception { initDisplay(); initGL(); // allocate a texture big enough to hold screen image // the texure will be square, the dimensions will be some power of two, as // large or larger than the width of the screen. screenTextureHandle = makeTextureForScreen(displayMode.getWidth()); // capture the screen now to initialize the texture image // It's a good idea to call glClear() before capturing the screen for the first time. frameSave(screenTextureHandle); // Random value for rotation increment: .05 - 1.0 rotationAmount = (float) (.05 + (Math.random()*.05)); } /** * Create an OpenGL display, in this case a fullscreen window. * @throws Exception */ private void initDisplay() throws Exception { // get all possible display resolutions DisplayMode d[] = Display.getAvailableDisplayModes(); // find a resolution we like for (int i = 0; i < d.length; i++) { if (d[i].getWidth() == 1024 && d[i].getHeight() == 768 && d[i].getBitsPerPixel() == 32) { displayMode = d[i]; break; } } // set the display to the resolution we picked Display.setDisplayMode(displayMode); Display.setTitle(windowTitle); // if true, set to full screen, no chrome Display.setFullscreen(false); Display.setVSyncEnabled(true); // create the window Display.create(); } /** * Initialize OpenGL * */ private void initGL() { // Select the Projection Matrix (controls perspective) GL11.glMatrixMode(GL11.GL_PROJECTION); GL11.glLoadIdentity(); // Reset The Projection Matrix // Define perspective GLU.gluPerspective( 45.0f, // Field Of View (float)displayMode.getWidth() / (float)displayMode.getHeight(), // aspect ratio 0.1f, // near Z clipping plane 100.0f); // far Z clipping plane // Select The Modelview Matrix (controls model orientation) GL11.glMatrixMode(GL11.GL_MODELVIEW); // No need for depth, composition is flat GL11.glDisable(GL11.GL_DEPTH_TEST); // set the background color GL11.glClearColor(.1f, .1f, .12f, 1); GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT); // To create transparencies (alpha blending) GL11.glEnable(GL11.GL_BLEND); GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); // need textures on for frameDraw() to work GL11.glEnable(GL11.GL_TEXTURE_2D); } /** * Handle keyboard input. Just check for escape key or user * clicking to close the window. */ private void mainloop() { if(Keyboard.isKeyDown(Keyboard.KEY_ESCAPE)) { // Escape is pressed done = true; } if(Display.isCloseRequested()) { // Window is closed done = true; } } /** * Render the scene. */ private void render() { rotation += .05f; rotation2 += rotationAmount; // Reset the Modelview matrix GL11.glMatrixMode(GL11.GL_MODELVIEW); GL11.glLoadIdentity(); // draw the screen image back to the framebuffer before we draw anything else // frameDraw() will switch the Projection matrix to ortho mode to draw the screen image frameDraw(screenTextureHandle); //marbleTextureHandle); // // be sure we're in modelview mode (frameDraw switches to Projection matrix) GL11.glMatrixMode(GL11.GL_MODELVIEW); // Place the 'eye' GLU.gluLookAt( 0f, 0f, 5f, // eye position 0f, 0f, 0f, // target to look at 0f, 1f, 0f); // which way is up // rotate scene GL11.glRotatef(rotation*3.3f, 0,0,1); GL11.glTranslatef(2,0,0); // draw a red quad GL11.glColor4f(R,0f,0f,.03f); GL11.glPushMatrix(); { GL11.glRotatef(rotation*.7f, 0,1,1); GL11.glTranslatef(0,.5f,0); GL11.glRotatef(rotation2, 0,0,1); drawQuad(); } GL11.glPopMatrix(); // rotate more GL11.glRotatef(rotation2*4.7f, 0,0,1); GL11.glTranslatef(1,0,0); // draw a green quad GL11.glColor4f(0f,G,0f,.03f); GL11.glPushMatrix(); { GL11.glRotatef(rotation2*2f, 0,0,1); GL11.glTranslatef(-.5f,-.5f,0); GL11.glRotatef(rotation2, 0,0,1); drawQuad(); } GL11.glPopMatrix(); // draw a blue quad GL11.glColor4f(0f,0f,B,.03f); GL11.glPushMatrix(); { GL11.glRotatef(rotation2, 1,0,1); GL11.glTranslatef(.5f,-.5f,0); GL11.glRotatef(rotation2, 0,0,1); drawQuad(); } GL11.glPopMatrix(); // save the screen image to a texture // do this after you've drawn everything you want to save frameSave(screenTextureHandle); } /** * draw a 1x1 square */ public void drawQuad() { GL11.glBegin(GL11.GL_QUADS); { GL11.glTexCoord2f(0, 0); GL11.glVertex3f(-1.0f,-1.0f, 0.0f); // Bottom Left GL11.glTexCoord2f(1, 0); GL11.glVertex3f( 1.0f,-1.0f, 0.0f); // Bottom Right GL11.glTexCoord2f(1, 1); GL11.glVertex3f( 1.0f, 1.0f, 0.0f); // Top Right GL11.glTexCoord2f(0, 1); GL11.glVertex3f(-1.0f, 1.0f, 0.0f); // Top left } GL11.glEnd(); } /** * Cleanup all the resources. * */ private void cleanup() { Display.destroy(); } /** * Allocate a texture (glGenTextures) and return the handle to it. */ public static int allocateTexture() { IntBuffer textureHandle = allocInts(1); GL11.glGenTextures(textureHandle); return textureHandle.get(0); } public static final int SIZE_INT = 4; public static final int SIZE_BYTE = 4; public static IntBuffer allocInts(int howmany) { return ByteBuffer.allocateDirect(howmany * SIZE_INT).order(ByteOrder.nativeOrder()).asIntBuffer(); } public static ByteBuffer allocBytes(int howmany) { return ByteBuffer.allocateDirect(howmany * SIZE_BYTE).order(ByteOrder.nativeOrder()); } //------------------------------------------------- // Functions to save framebuffer to texture //------------------------------------------------- /** * Create a texture large enough to hold the screen image. * * In glTexImage2D() use RGBA8 format * to insure colors are copied exactly. This insures that each color component (red, green, blue) * has 8 bits. RGB or RGBA may not have a full 8 bits per color component. * * Use GL_NEAREST for magnification. This prevents slight color shifts that may results from * color interpolation when the texture is rendered. * * @see frameSave() * @see frameDraw() */ public int makeTextureForScreen(int screenSize) { // get a texture size big enough to hold screen (512, 1024, 2048 etc.) screenTextureSize = getPowerOfTwoBiggerThan(screenSize); System.out.println("GLApp.makeTextureForScreen(): made texture for screen with size " + screenTextureSize); // get a new empty texture int textureHandle = allocateTexture(); ByteBuffer pixels = allocBytes(screenTextureSize*screenTextureSize*SIZE_INT); // 'select' the new texture by it's handle GL11.glBindTexture(GL11.GL_TEXTURE_2D,textureHandle); // set texture parameters GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_REPEAT); GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_REPEAT); // use GL_NEAREST to prevent blurring during frequent screen restores GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST); GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST); // Create the texture from pixels: use GL_RGBA8 to insure exact color copy GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, screenTextureSize, screenTextureSize, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, pixels); return textureHandle; } /** * Find a power of two big enough to hold the given dimension. Ie. * to make a texture big enough to hold a screen image for an 800x600 * screen getPowerOfTwoBiggerThan(800) will return 1024. *
* @see makeTextureForScreen() * @param dimension * @return a power of two bigger than the given dimension */ public static int getPowerOfTwoBiggerThan(int dimension) { for (int exp=1; exp <= 32; exp++) { if (dimension <= Math.pow(2, exp)) { return (int)Math.pow(2, exp); } } return 0; } /** * Save entire screen image to a texture. Will copy entire screen even * if a viewport is in use. Texture param must be large enough to hold * screen image (see makeTextureForScreen()). * * @param txtrHandle texture where screen image will be stored * @see frameDraw() * @see makeTextureForScreen() */ public void frameSave(int txtrHandle) { GL11.glColor4f(1,1,1,1); // turn off alpha and color tints GL11.glReadBuffer(GL11.GL_BACK); GL11.glBindTexture(GL11.GL_TEXTURE_2D,txtrHandle); // Copy screen to texture GL11.glCopyTexSubImage2D(GL11.GL_TEXTURE_2D, 0, 0,0, 0,0, displayMode.getWidth(),displayMode.getHeight()); //!!! whole frame } /** * Draw the screen-sized image over entire screen area. The screen image * is stored in the given texture at 0,0 (see frameSave()) and has the * same dimensions as the current display mode (DM.getWidth(), DM.getHeight()). *
* Reset the viewport and ortho mode to full screen (viewport may be * different proportion than screen if custom aspectRatio is set). Draw the * quad the same size as texture so no stretching or compression of image. * * @param txtrHandle */ public void frameDraw(int txtrHandle) { // keep it opaque, be sure texturing is enabled GL11.glDisable(GL11.GL_BLEND); GL11.glEnable(GL11.GL_TEXTURE_2D); // set viewport to full screen (need to handle custom shaped viewport) //GL11.glViewport(0, 0,displayMode.getWidth(), displayMode.getHeight()); // set ortho view to full screen and draw quad GL11.glMatrixMode(GL11.GL_PROJECTION); GL11.glPushMatrix(); // preserve current projection { GL11.glLoadIdentity(); // clear the projection matrix GL11.glOrtho(0, displayMode.getWidth(), 0, displayMode.getHeight(), -1, 1); // turn on 2D mode full screen GL11.glColor4f(1,1,1,1); drawQuad(txtrHandle, 0, 0, screenTextureSize, screenTextureSize); // draw the full screen image } GL11.glPopMatrix(); // restore projection view // put these settings back on GL11.glDisable(GL11.GL_TEXTURE_2D); GL11.glEnable(GL11.GL_BLEND); } /** * Draw arbitrary sized quad at given xy coords, Apple the given texture. */ public static void drawQuad(int textureHandle, int x, int y, float w, float h) { GL11.glBindTexture(GL11.GL_TEXTURE_2D,textureHandle); // draw textured quad GL11.glBegin(GL11.GL_QUADS); GL11.glTexCoord2f(0f, 0f); GL11.glVertex3f( (float)x, (float)y, (float)0); GL11.glTexCoord2f(1f, 0f); GL11.glVertex3f( (float)x+w, (float)y, (float)0); GL11.glTexCoord2f(1f, 1f); GL11.glVertex3f( (float)x+w, (float)y+h, (float)0); GL11.glTexCoord2f(0f, 1f); GL11.glVertex3f( (float)x, (float)y+h, (float)0); GL11.glEnd(); } }