I’ve stumbled upon some weird behavior from accumulation buffers when used with GLCanvas and doing multiple accumulation calls.
The application runs as expected and does as it’s supposed to do… until you minimize it.
When you do that it can produce the following results:
- Hangs the computer.
- Causes a blue screen of death.
- Causes a system wide loss of the graphics device from which it sometimes doesn’t recover.
- Restarts the computer
The criteria which need to be met to reproduce the “behavior” are:
- The use of GLCanvas. If you use GLJPanel it doesn’t work.
- Using Accumulation buffers.
- Having multiple accumulation calls in the display callback.
- Minimizing the JFrame or Frame when everything is running.
The following code is enough to reproduce it:
(I modified the SimpleJOGL application to do my bidding :D)
package org.yourorghere;
import com.sun.opengl.util.Animator;
import java.awt.Frame;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.media.opengl.DebugGL;
import javax.media.opengl.GL;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLCanvas;
import javax.media.opengl.GLCapabilities;
import javax.media.opengl.GLEventListener;
import javax.media.opengl.GLJPanel;
import javax.media.opengl.glu.GLU;
/**
* SimpleJOGL.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 SimpleJOGL implements GLEventListener {
public static void main(String[] args) {
Frame frame = new Frame("Simple JOGL Application");
GLCapabilities caps = new GLCapabilities();
caps.setAccumBlueBits(32);
caps.setAccumGreenBits(32);
caps.setAccumRedBits(32);
//GLJPanel canvas = new GLJPanel(caps);
GLCanvas canvas = new GLCanvas(caps);
canvas.addGLEventListener(new SimpleJOGL());
frame.add(canvas);
frame.setSize(640, 480);
final Animator animator = new Animator(canvas);
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
// Run this on another thread than the AWT event queue to
// make sure the call to Animator.stop() completes before
// exiting
new Thread(new Runnable() {
public void run() {
animator.stop();
System.exit(0);
}
}).start();
}
});
// Center frame
frame.setLocationRelativeTo(null);
frame.setVisible(true);
animator.start();
}
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());
// Enable VSync
gl.setSwapInterval(1);
// Setup the drawing area and shading mode
gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
gl.glClearAccum(0.0f, 0.0f, 0.0f, 0.0f);
gl.glShadeModel(GL.GL_SMOOTH); // try setting this to GL_FLAT and see what happens.
}
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.glClear(GL.GL_ACCUM_BUFFER_BIT);
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();
gl.glAccum(GL.GL_ACCUM, 0.1f );
gl.glAccum(GL.GL_MULT, 1.1f );
gl.glAccum(GL.GL_RETURN, 1.0f);
// Flush all drawing operations to the graphics card
gl.glFlush();
}
public void displayChanged(GLAutoDrawable drawable, boolean modeChanged, boolean deviceChanged) {
throw new UnsupportedOperationException("Not supported yet.");
}
}
Although the example uses DebugGL it doesn’t matter if it set to the normal GL.
I suspect it has something to do with values going into ranges which aren’t supported.
I’ve tried the following code on 4 different PCs and although all of them displayed different things it isn’t exactly the behavior that’s desired.
Perhaps I’m doing something wrong, that’s a possibility but shouldn’t it display a warning, throw an exception, reject a request, clamp values to appropriate values, display a debug message or something instead of flat out crashing, glitching or dying?
The only “fixes” I could think of were:
- Use GLJPanel, which is unfortunately too slow.
- Use only two accumulation calls, this doesn’t give me the desired effect.
- Avoid using accumulation buffers and switch to FBOs and shaders, although it’s possible it’s a lot more complex and isn’t supported by a couple of target computers
- Register a Listener which fires when the Frame minimizes, it doesn’t work because the event arrives later than the actual minimizing
If someone knows a way to fix it or a way to avoid the problem they’re welcome to share.