Weird behavior from Accumulation Buffers

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.

If you have access to the JFrame or Frame holding the GLCanvas, you can call isShowing() in the display() method. If this is false, then just don’t render anything. I haven’t tested it, but that method may give more responsive feedback than an event listener.

I altered the SimpleJOGL application to check whether the frame is showing or not.


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);
        // Center frame
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
        while(true) {
            if(frame.isShowing()) {
                canvas.display();
            }
        }
    }

    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.");
    }

}

I’ve tried it before with the whole range of isActive(), hasFocus(), isShowing() and isEnabled().
It still seems to crash though.
I wonder if it has something to do with context loss and etc.

Are accumulation buffers really that rarely used that nobody stumbled onto this or at least had the same problem?

One might argue that the gfx cards not supporting FBOs these days are either crap, or too slow to render the effects which would normally require the accum buffers.

Otherwise, it might be a bug in JOGL. You might try LWJGL as it has no AWT backing. If it crashes there too, it is a driver bug.