I am developing a 3D visualization application using LWJGL 2.8.5. After reading the first tutorials from the project home page I also went deeper in my analysis reading an OpenGL book. I see the typical procedure in OpenGL is drawing the scene in the init function, then simply calling the update of display in a loop.
However, when I try this with LWJGL I get a flickering effect in the display. The only way to eliminate the flickering is to redraw the scene in the display updating cycle. Why is this happening?
To better explain my problem I have created a simple class reproducing the problem. It simply draws a quad in the center of the screen, then it goes into the endless screen update loop.
Note that if I uncomment the draw call within the cycle, then flickering disappears everything works. Why?
Is there anything wrong with my expectation to only draw the objects once and just move the camera to get a different view of a static scene?
Here it is the code:
package test;
import org.lwjgl.LWJGLException;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.opengl.GL11;
import org.lwjgl.util.glu.GLU;
import static org.lwjgl.opengl.GL11.*;
public class DisplayTest
{
public static void initGL()
{
GL11.glViewport(0, 0, 640, 480);
glMatrixMode(GL_PROJECTION);
GLU.gluPerspective(45.0f, 640f/480f,0.1f, 100.0f);
draw();
}
public static void draw()
{
glMatrixMode(GL_MODELVIEW);
GL11.glLoadIdentity(); // Reset The Current Modelview Matrix
GL11.glTranslatef(0, 0, -6.0f);//Place at the center at -6 depth units
//Start drawing a quad
//--------------------------------------------------
GL11.glBegin(GL11.GL_QUADS);
int size=1;
GL11.glColor3f(.3f, .5f, .8f);
GL11.glVertex3f(-size/2f,-size/2f,+size/2f);
GL11.glVertex3f(+size/2f,-size/2f,+size/2f);
GL11.glVertex3f(+size/2f,+size/2f,+size/2f);
GL11.glVertex3f(-size/2f,+size/2f,+size/2f);
glEnd();
}
public static void main(String[] args)
{
try
{
// Sets the width of the display to 640 and the height to 480
Display.setDisplayMode(new DisplayMode(640, 480));
// Sets the title of the display
Display.setTitle("Drawing a quad");
// Creates and shows the display
Display.create();
}
catch (LWJGLException e)
{
e.printStackTrace();
Display.destroy();
System.exit(1);
}
initGL();
// While we aren't pressing the red button on the display
while (!Display.isCloseRequested())
{
//draw();
// Update the contents of the display and check for input
Display.update();
// Wait until we reach 60 frames-per-second
Display.sync(60);
}
// Destroy the display and render it invisible
Display.destroy();
System.exit(0);
}
}
By default OpenGL maintains two buffers: a front buffer and a back buffer. The front buffer is what is displayed while the back buffer is what you draw on. This is called double buffering and prevents partially rendered scenes from being shown to the user. Whenever you call Display.update(), lwjgl tells opengl to move the content of the back buffer to the front buffer for display. This is either done by copying the data, or by marking the old front buffer as new back buffer and the old back buffer as new front buffer (the buffers are swaped).
This is geared towards the normal situation e.g. in games where you want to draw something new like 60 times a second. Now if you don't actually draw anything, but still call Display.update() (e.g. to get Input), the buffers are still moved. Now depending on how this is implemented, either you still see your old image (if the data is simply copied, because your back buffer then still contains the data you rendered), or you alternate with each Display.update() between the buffer you have drawn on and the buffer that was the front buffer at the time that you rendered (which is likely just black). This leads to the flickering you are seeing, as the image is only shown every second frame (original back buffer), and every other frame the black original front buffer is seen.
With some driver implementations (e.g. from Nvidia), it seems that Windows can even draw on the active front buffer, so dialog boxes can show up and be displayed even after are closed if you don't redraw the scene.
The cleanest solution to this is to simply render to a texture (works since OpenGL 1.1) or a renderbuffer (introduced in OpenGL 3.0) in your init, and then render this texture each frame. This is really the solution that the OpenGL designers had in mind for this type of thing.
Edit: Either you uncomment the draw method in the above code, or in case that is no option because of render time, you have to render to a texture. Using OpenGL 3 or above you would need to put something like this in your init:
fbo = GL30.glGenFramebuffers();
GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, fbo);
rbo = GL30.glGenRenderbuffers();
GL30.glBindRenderbuffer(GL30.GL_RENDERBUFFER, rbo);
GL30.glRenderbufferStorage(GL30.GL_RENDERBUFFER, GL11.GL_RGBA8, 640, 480);
GL30.glFramebufferRenderbuffer(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL30.GL_RENDERBUFFER, rbo);
assert(GL30.glCheckFramebufferStatus(GL30.GL_FRAMEBUFFER) == GL30.GL_FRAMEBUFFER_COMPLETE);
GL30.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, fbo);
GL20.glDrawBuffers(GL30.GL_COLOR_ATTACHMENT0);
GL11.glClear(GL11.GL_COLOR_BUFFER_BIT);
drawScene(); //draw here
your draw method to call every frame would then be simplified to a simple and fast copy from the framebuffer fbo:
GL30.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, fbo);
GL11.glReadBuffer(GL30.GL_COLOR_ATTACHMENT0);
GL30.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, 0);
GL20.glDrawBuffers(GL11.GL_BACK_LEFT);
GL30.glBlitFramebuffer(0, 0, 640, 480, 0, 0, 640, 480, GL11.GL_COLOR_BUFFER_BIT, GL11.GL_NEAREST);
GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);