[BUG] AWTGLCanvas & swing painting issues

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

Previous topic - Next topic

3Ddeveloper

Hi, I am new to this forum and I am little stuck with my problem. I am using multiple views in 3D editor like program and combine it with lightweight swing components .. my views are put into JPanel and panels are inside of JSplitPane. When panes are moved or window is resized I have an ugly lazy repainting and AWTGLCanvas paints through other components like pane dividers or icons in other parts of GUI. I spend so many hours trying to trick it out somehow .. but I left it with just slight ugly repainting when changing proportions .. not looking very good but is acceptable for now, I recently added new special view which is inside of JScrollPane .. when canvas is rolled off from visible part it paints through other components and that is not acceptable .. I read an article "http://www.oracle.com/technetwork/articles/java/mixing-components-433992.html" that says that these issues with heavyweight components might be an history but I am meeting the same problem now with AWTGLCanvas. Has any of you found a way to solve this to get smooth integration with lightweighted swing gui without any ugly repainting pain ?

basil

It's not really easy to combine those two. Usually you have repainting, resizing and focus problems. can you provide more details and maybe some demo code? Hard to tell where to put the span on.

3Ddeveloper

.. I'll try to create sample application, will post it soon ..

3Ddeveloper

// just move with bottom vertical scrollbar and then try the same with using mouse wheel ..
// you can clearly see that red box (AWTGLCanvas) is painting over scrollbars or even top panel
// I call update every time the panel owning the canvas need to be repainted, cause canvas is getting less paint calls than it should get.

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
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 AWTGLCanvas canvas;

    public static void main (String[] args) {
       
        final JPanel panel_canvas = new JPanel ();
       
        try {
            panel_canvas.add (canvas = new AWTGLCanvas (new PixelFormat (8, 8, 0, 4)) {

                final Dimension size = new Dimension (100, 100);
               
                @Override
                public void initGL () {
                   
                    setPreferredSize (size);
                    setSize          (size);
                   
                    glDisable (GL_DEPTH_TEST);
                    glDisable (GL_CULL_FACE);
                }

                @Override
                public void paintGL () {
                                       
                    try {
                       
                        glViewport (0, 0, getWidth (), getHeight ());

                        glColor3f (1,0,0);

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

                        glFinish ();
                       
                        swapBuffers ();
                       
                    } catch (LWJGLException ex) {}
                }
               
            }, BorderLayout.CENTER);
           
        } catch (Exception e) {};
       
        JPanel panel_top      = new JPanel (new FlowLayout ());
        JPanel panel_bottom   = new JPanel (new BorderLayout ()) {
           
            @Override
            public void paint (Graphics g) {
               
                canvas.update (g);
            }
        };
       
        panel_top   .setPreferredSize (new Dimension (200, 300));
        panel_bottom.setPreferredSize (new Dimension (200, 300));
       
        panel_bottom.add (panel_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);       
       
        JFrame frame = new JFrame ();
       
        frame.add (splitpane);       
        frame.pack ();
       
        frame.setVisible (true);
    }
}

basil

wow, I see, that's pretty bad! Let me try to get this working :)

*edit*

at the first glance, when I replaced the JPanel with java.awt.Panel things started to be more fluid. Is is, pretty sure an issue related to mixing awt+swing components. I'll get back to you later.

broumbroum

If I could add some comments out of the snap of code you let here : its globally ok for the layout, but since AWTGLCanvas implements the original AWT Canvas, you have to move the update(g) into a self managed Thread that invokes AWT/Swing at fixed Rate (since openGL rendering is active). Like that piece of code :
main(String args|]){

TimerTask tt = new TimerTask() {  void run() {
// this invokes update(g) on the EDT 
    SwingUtilities.invokeAndWait(new Runnable(){ // Wait, AWT/Swing is running on the same Thread now
        public void run() {
            yourAWTGLCanvas.update(yourAWTGLCanvas.getGraphics());
        }
    });
}};
// by using the Timer, it ensures that AWT/Swing is able to do the required updates, between each TimerTask execution.
Timer t = new Timer();
t.scheduleAtFixedRate(tt,0,30);

}


That way only, your canvas will be compliant with the AWT specifications, running on the EDT.

3Ddeveloper

Hi, but I don't want active rendering .. this is not a video game or some persistent animation .. I only need to repaint it once at a time like other swing components .. I have already tried active rendering .. there is not problem with triggering of painting .. problem is that it paints over other objects .. If the canvas moves other object maybe do not get repaint events or simply cannot paint over that occupied portion of screen. Interesting is that is not at 100% of time, sometimes is renders ok, and sometimes it paints over regardless of how often paintGL is called. Seems like it do not updates it internal visible region correctly or something like that .. I would only wonder if there is some way how to fix it some walk around ?

broumbroum

You have actually such a synchronizing issue over the AWT/Swing rendering Thread. My previous post was actually the "workaround" you were looking for, because "passive" rendering is not possible with openGL. It's hardware accelerated, hence the video memory is highly volatile, and must be refreshed quickly, even if "no change" was made.
JPanel panel_bottom   = new JPanel (new BorderLayout ()) {
            
/** (unuseful !)*/
            @Override
            public void paint (Graphics g) { 
                super.paint(g); //  ?
                canvas.update (g); 
            }
        };
       

Anyway, if the AWTGLCanvas is correctly added to the JPanel, it's basically wrong to recall update(g) by overriding the JPanel paint(g), because AWT will do it by default (in either case make a call to the super.paint(g) and to the super.function() whenever you override methods).

If you don't like the self managed thread approach, it's also possible to call repaint() periodically over the canvas. AWT/Swing internally handles everything in term of synchronization of the screen refresh, when it is correctly managed.
:)

3Ddeveloper

I guess you didn't run the app I posted .. I know OpenGL very well .. it is not active by itself, you have to update screen by yourself .. you can paint once if you want, it is on the system/driver how it handles resulting framebuffer .. Repainting canvas forever doesn't help, it will only make things worse and it is really bad waste of processing power plus it doesn't solve my issue either. If you remove update I put into paint method you will see it doesn't repaint at all or very rarely .. so it doesn't do it by default, run the app and see.

broumbroum

Hi ,
just compiled your code with Lwjgl 2.7 and I 've seen the red square sometimes overlapping with the upper splitted panel.

It actually looks like you really can disable the call to update(g) and put it into a TimerTask like I suggested it . [edit] Now the canvas is refreshed every 30ms, which looks like 30-40 fps. the Timer period is involved for increasing the refresh speed. Scrolling (with or without mouse) now looks fine.

Notice : I used my API framework which just eases the LWJGL integration, native loading and other Java environment task, like the Swing EDT issue. Just in case you want to see the code I used http://pastebin.com/Fh8mYP6w
:D

3Ddeveloper

.. try scrollwheel .. and if it still look fine to you try harder :)

NOTE: and yes I forgot super.paint (g) .. but that not change things much :)

EDIT: another tip, grab divider and move down .. that goes pretty easy.

broumbroum

I think it's alright if you just agree with the Timer solution.  ::)
quote from my previous post (edited): Now the canvas is refreshed every 30ms, which looks like 30-40 fps. the Timer period is involved for increasing the refresh speed. Scrolling (with or without mouse wheel) now looks fine.

3Ddeveloper

.. ok, so let mi explain how I see it ..  all you do is less painting in critical moments and mostly painting when it is not needed .. the result is that portion of the screen is more probably overwritten by swing components .. Actually I have tried it and saw the problem is still there only if you paint it less, it is more probable that components will paint over it and will not be overpainted with canvas .. the bigger the interval of timer the less repainting of canvas .. and problems happen more rarely or is less visible but it is still there, so I do not think this is a solution .. solution is something that work 100% of time. If canvas was painted last and there is no other overlying component triggered to repaint .. it keeps like that no matter how many times per second you repaint it cause what you need to repaint is the lightweight component that should appear above canvas ..

broumbroum

Well, I do understand clearly what you mean. You talk about a pattern that is really hard to code. It's similar to rendering components in the volatile offscreen fashion. But that is harder to code, since you'd have to make a copy of the OpenGL frame buffer into a Buffered or VolatileImage (to benefit of a hardware acceleration and a perfect copy), which would be rendered as needed. But if the contents should change over time (e.g. your square changes of its color), an AWTEvent would be triggered and everything need to be re-rendered again.
IMO that involves the repeatability case in the invariance rules of OpenGL (first paragraph). The obvious and most fundamental case is repeatability. A conforming OpenGL implementation generates the same results each time a specific sequence of commands is issued from the same initial conditions. Although such repeatability is useful for testing and verification, it's often not useful to application programmers, because it's difficult to arrange for equivalent initial conditions. For example, rendering a scene twice, the second time after swapping the front and back buffers, doesn't meet this requirement. So repeatability can't be used to guarantee a stable, double-buffered image.Such a behaviour is not useful for applications in the OpenGL environment.

3Ddeveloper

I am not really sure what you mean with the bottom part but I would like to avoid copying data to main memory .. of course there are other ways to do what I want, but are less flexible harder to code and of course slower .. that is why I want to use OpenGL .. data is in graphics memory and all I need is to paint it and scale it down .. so this is the most direct and fastest way. I use shared context so everything is prepared and reused. I want to use AWTGLCanvas cause it is more perspective for future extensions of functionality .. I can do some operations using OpenGL to paint what I want the best possible way. But the problem I am having is more about heavyweight vs lightweight .. a guess it would do the same with awt.Panel instead of canvas ..
In this article they discuss it  "http://www.oracle.com/technetwork/articles/java/mixing-components-433992.html"  but it is probably not that easy as it seems. I have "fixed" problem for now with little funny way .. simply hiding canvas when it comes to trouble .. it looks better, not like a bug but it is still not what it should be. I found that validating scroll pane helps, but not for all cases.