[BUG] AWTGLCanvas & swing painting issues

Started by 3Ddeveloper, February 01, 2013, 16:21:15

Previous topic - Next topic

basil

I dont really agree. OpenGL is just a general purpose 'graphics library'. There are many applications that would utilise a "paint when required" in a good way. A Timer is just a fixed update rate what is common in games.

I also found the problem not really within mixing heavy and lightweight components but simply in the direct call to canvas.update. Check this out, I wrote more comments about it there :

//~--- non-JDK imports --------------------------------------------------------

import org.lwjgl.LWJGLException;
import org.lwjgl.opengl.AWTGLCanvas;
import org.lwjgl.opengl.PixelFormat;

import static org.lwjgl.opengl.GL11.*;

//~--- JDK imports ------------------------------------------------------------

import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.FlowLayout;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import java.nio.file.Paths;

import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.SwingUtilities;

//~--- classes ----------------------------------------------------------------

/**
 */
public class CanvasTest implements Runnable
{

  //~--- fields ---------------------------------------------------------------

  private AWTGLCanvas canvas;

  //~--- methods --------------------------------------------------------------

  /**
   *
   * @param args String[]
   */
  public static void main(String[] args)
  {
    System.setProperty("org.lwjgl.librarypath", Paths.get("bin", "windows").toAbsolutePath().toString());    // if natives are at bin/windows

    //
    SwingUtilities.invokeLater(new CanvasTest());                                                            // move all swing init calls into EDT. just to be sure.
  }

  /**
   */
  @Override
  public void run()
  {

    try
    {
      canvas = new AWTGLCanvas(new PixelFormat(8, 8, 0, 4))
      {
        @Override
        public void initGL()
        {
          glDisable(GL_DEPTH_TEST);
          glDisable(GL_CULL_FACE);
          System.out.println("gl init called. " + Thread.currentThread().getId() + " " + Thread.currentThread().getName());
        }
        @Override
        public void paintGL()
        {

          try
          {
            glViewport(0, 0, getWidth(), getHeight());
            glColor3d(Math.random() * .5 + .25, Math.random() * .5 + .25, Math.random() * .5 + .25);    // randomise color so we can see the updates.
            glBegin(GL_QUADS);
            glVertex2f(-1, -1);
            glVertex2f(1, -1);
            glVertex2f(1, 1);
            glVertex2f(-1, 1);
            glEnd();
            glColor3f(1f, 0, 1f);
            glLineWidth(3f);
            glBegin(GL_LINE_STRIP);
            glVertex2f(-1, -1);
            glVertex2f(1, -1);
            glVertex2f(1, 1);
            glVertex2f(-1, 1);
            glEnd();
            glFinish();
            swapBuffers();
            System.out.println("gl buffers swapped. " + Thread.currentThread().getId() + " " + Thread.currentThread().getName());
          }
          catch(LWJGLException ex) {}

        }
      };

      canvas.setIgnoreRepaint(false);                      // use true if you want to block all paint calls to the canvas.
      canvas.setPreferredSize(new Dimension(100, 100));    // moved from inside initGL() so its called once canvas is displayed by AWT
      canvas.setSize(new Dimension(100, 100));
      canvas.setBackground(Color.green);                   // if you see green on the canvas, it means the canvas shape is not updated correctly. I am not sure how to fix it.
    }
    catch(Exception e)
    {
      e.printStackTrace(System.err);
    }

    JPanel panel_top    = new JPanel(new FlowLayout(FlowLayout.CENTER, 0, 0));
    JPanel panel_bottom = new JPanel(new FlowLayout(FlowLayout.CENTER, 0, 0));

    panel_top.setPreferredSize(new Dimension(200, 300));
    panel_bottom.setPreferredSize(new Dimension(200, 300));
    panel_top.setBackground(Color.lightGray);
    panel_bottom.setBackground(Color.lightGray);

    //
    JPanel dummy = new JPanel();    // just a red square

    dummy.setBackground(Color.red);
    dummy.setPreferredSize(new Dimension(100, 100));
    dummy.setSize(new Dimension(100, 100));

    //
    panel_top.add(dummy);
    panel_bottom.add(canvas);

    JScrollPane scrollpane_top    = new JScrollPane(panel_top);
    JScrollPane scrollpane_bottom = new JScrollPane(panel_bottom);
    JSplitPane  splitpane         = new JSplitPane(JSplitPane.VERTICAL_SPLIT);

    splitpane.setPreferredSize(new Dimension(200, 300));
    splitpane.setTopComponent(scrollpane_top);
    splitpane.setBottomComponent(scrollpane_bottom);
    splitpane.setContinuousLayout(true);
    splitpane.setResizeWeight(0.5f);

    //
    scrollpane_bottom.getViewport().addChangeListener(new ChangeListener()               // makes the canvas 'tight'. remove and watch the top pink line not beeing painted when scrolling up to top
    {                                                                                    // also the canvas doesn't 'stick out' when you scroll down
      @Override
      public void stateChanged(ChangeEvent e)
      {
        ((JComponent)e.getSource()).getParent().getParent().getParent().revalidate();    // not sure if this is the best way but it works. maybe there is a better. I gues I try to get to the frame.
      }
    });

    //
    splitpane.addPropertyChangeListener("dividerLocation", new PropertyChangeListener()    // makes the canvas NOT overlap bottom scrollbar when moving the divider to bottom.
    {
      @Override
      public void propertyChange(PropertyChangeEvent e)
      {
        ((JComponent)e.getSource()).getParent().revalidate();
      }
    });

    JFrame frame = new JFrame();

    frame.addComponentListener(new ComponentAdapter()
    {
      @Override
      public void componentResized(ComponentEvent e)
      {

        // ???
      }
    });

    //
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setLocation(400, 400);
    frame.add(splitpane);
    frame.pack();
    frame.setVisible(true);

    // one thing stay ugly; if you resize the frame you see the canvas overlapping components badly.
    // on the other hand, watch how good the canvas position is updated, compared to the upper red JPanel.

    /*  */

    // in case canvas is not painted by AWT
    if(canvas.getIgnoreRepaint())
    {
      Thread glUpdater = new Thread(new Runnable()
      {
        @Override
        public void run()
        {
          while(true)
          {
            try
            {
              canvas.paint(null);
              Thread.sleep(250);    // one could probably insert a ReentrantLock.Condition here and trigger updates properly from inside the EDT.
            }
            catch(InterruptedException ex) {}
          }
        }
      }, "gl-updater");

      glUpdater.setDaemon(true);
      glUpdater.start();
    }

  }
}


It works quiet ok but there are still things to be done correctly. I think there is a way to interleave the GL with the AWT paints to get the canvas beeing always rendered correctly.

bas

*edit*

before it gets confusing, I had issues with LWJGL+Canvas before too. They disappeared once I updated JVM to > JDK 1.7.0_5 and LWJGL 2.9.0.

3Ddeveloper

Hi, thanx for your help. I have already tried validating parent components and it really helps, is some cases it removes problem totally but it some other not .. they suggest it in the article I have posted link to. But I still got some ugly shit. Now I have tried validating on scrollbar actions and it is even better, maybe I can use it like this. I still see it repaints over but then disappears very fast .. Almost perfect, thank you :)

NOTE: .. and to the mixing of lightweight & heavyweight .. this is really problem related to it. There is the article I have posted already few times "http://www.oracle.com/technetwork/articles/java/mixing-components-433992.html" ...

part under "Requirements" .. "The component hierarchy must be validated whenever it becomes invalid. If any part of the hierarchy is invalid, the mixing will not operate correctly." .. but I do not succeeded on acceptable level with validating .. I have tried validating almost everything right before canvas update .. but that was probably not best/correct way .. let find the right one

basil

you're right. Adding
e.getComponent().revalidate();
instead of
// ???
into the frame-resize-listener helps with the layout too, but introduces lots of flickering.

frame.createBufferStrategy(2);
gives a nice doublebuffered rendering on the frame but then we loose the layout again.

I am very sure there is a order of revalidating the correct components that would make this work perfect. :)

3Ddeveloper

OK, so I wrote this demo app which shows up problem which I have. It is little bit more simple in that it do not have any JScrollPane just split pane. To see the problem just grab divider and move it fast. The flickering is not the main problem, (I just do not want to spend too much time with just demo app) ... the problem is repainting over divider or other parts .. in my own app this problem looks when you hit print-screen like simply canvas is out of region. It looks like one frame out of sync. So where do you think I should put revalidate ? Try it out .. For me the problem is only fixed when there is 0% of overdrawing or flickering .. otherwise it seems to me the better solution is to simply hide canvas.
Here is the code:

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSplitPane;

import org.lwjgl.LWJGLException;
import org.lwjgl.opengl.AWTGLCanvas;
import org.lwjgl.opengl.PixelFormat;

import static org.lwjgl.opengl.GL11.*;

public class CanvasTest {
    
    public static class Custom_AWTGLCanvas extends AWTGLCanvas {
        
        public Color color;
        
        public Custom_AWTGLCanvas (Color color) throws LWJGLException {
            
            super (new PixelFormat (8, 8, 0, 4));
            
            setMinimumSize (new Dimension (1,1));
            
            this.color = color;
        }
        
        @Override
        public void initGL () {

            glDisable (GL_DEPTH_TEST);
            glDisable (GL_CULL_FACE);
        }

        @Override
        public void paintGL () {

            try {

                glViewport (0, 0, getWidth (), getHeight ());

                glColor3f (color.getRed (), color.getGreen (), color.getBlue ());

                glBegin (GL_QUADS);
                    glVertex2f (-1,-1);
                    glVertex2f ( 1,-1);
                    glVertex2f ( 1, 1);
                    glVertex2f (-1, 1);
                glEnd ();

                glFinish ();

                swapBuffers ();

            } catch (LWJGLException ex) {}
        }
    }    

    public static Custom_AWTGLCanvas canvas_top       = null;
    public static Custom_AWTGLCanvas canvas_bottom    = null;    
    
    public static void main (String [] args) {
        
        canvas_top       = null;
        canvas_bottom    = null;
        
        try {
            
            canvas_top       = new Custom_AWTGLCanvas (new Color (0, 1, 0));
            canvas_bottom    = new Custom_AWTGLCanvas (new Color (1, 0, 0));
            
        } catch (Exception e) {}
        
        JPanel panel_top      = new JPanel (new BorderLayout ());
        JPanel panel_bottom   = new JPanel (new BorderLayout ());
        
        panel_top    .add (canvas_top);
        panel_bottom .add (canvas_bottom);
        
        final JFrame frame = new JFrame ();
        
        final JSplitPane splitpane = new JSplitPane (JSplitPane.VERTICAL_SPLIT);
        
        splitpane.addPropertyChangeListener (new PropertyChangeListener () {

            @Override public void propertyChange (PropertyChangeEvent pce) {
                
                frame.validate ();
                splitpane.validate ();
                
                canvas_top      .update (canvas_top     .getGraphics ());
                canvas_bottom   .update (canvas_bottom  .getGraphics ());
            }
        });
        
        splitpane.setPreferredSize (new Dimension (200, 300));
        
        splitpane.setTopComponent       (panel_top);
        splitpane.setBottomComponent    (panel_bottom);
        
        splitpane.setContinuousLayout (true);
        splitpane.setResizeWeight (0.5f);        
                
        frame.add (splitpane);        
        frame.pack ();
        
        frame.setVisible (true);
    }
}