jogl
  1. jogl
  2. JOGL-316

Multi-Head issues, identical to issue 241

    Details

    • Type: Bug Bug
    • Status: Resolved
    • Priority: Blocker Blocker
    • Resolution: Fixed
    • Affects Version/s: current
    • Fix Version/s: milestone 1
    • Component/s: jogl
    • Labels:
      None
    • Environment:

      Operating System: Linux
      Platform: PC

    • Issuezilla Id:
      316

      Description

      Basically, I am creating a JFrame and adding a GLCanvas to it. This sometimes
      fails due to java.awt.Component.checkGD() throwing an IllegalArgumentException.
      This appears to be due to the fact that the GLCanvas and the JFrame have
      different GraphicsDevice instances associated with them. Component.checkGD()
      simply checks the id string associated with the device for equality:

      void checkGD(String stringID) {
      if (graphicsConfig != null) {
      if (!graphicsConfig.getDevice().getIDstring().equals(stringID))

      { throw new IllegalArgumentException( "adding a container to a container on a different GraphicsDevice"); }

      }
      }

      On my system (back when I had two heads...) I was using NVidia's driver
      (currently 100.14.11) in TwinView mode with Xorg (7.0, 7.1) on Linux 2.6.22-x64,
      I see two GraphicsDevices, one corresponding to each head. These have ID
      strings something like ':0.0' and ':0.1' respectively.

      It may be possible to work around the problem by detecting the graphics device
      used by the container I am adding the GLCanvas to, and setting the GC on the
      GLCanvas (during construction?), however, since the GLCanvas seems to work fine
      when moving the canvas (and parent container) across heads, I suspect that it
      may be possible to solve this within JOGL...

      The attached code can be compiled (with jogl/gluegen-rt in the classpath) and
      run with no arguments. It will attempt to identify a pair of GraphicsDevices
      that have identical id strings, except for the last part. If successful, it
      will open a JFrame on one of them, and attempt to add a GLCanvas. If no
      exception is thrown, it will move the JFrame to the other head and try to add
      another GLCanvas. This reliably demonstrates the issue on my system.

      FILE : JOGLWithNVidiaTwinView.java

      import java.awt.EventQueue;
      import java.awt.GraphicsDevice;
      import java.awt.GraphicsEnvironment;
      import java.awt.Rectangle;
      import java.awt.event.ComponentAdapter;
      import java.awt.event.ComponentEvent;
      import java.awt.event.ComponentListener;
      import java.awt.event.ContainerAdapter;
      import java.awt.event.ContainerEvent;
      import java.awt.event.ContainerListener;
      import java.awt.event.WindowAdapter;
      import java.awt.event.WindowEvent;
      import java.awt.event.WindowListener;

      import javax.media.opengl.GLCanvas;
      import javax.swing.JFrame;

      /**

      • Class to exhibit issue with JOGL on Linux/Xorg/NVidia/TwinView.
      • This will cause an IllegalArgumentException to be thrown when adding a
        GLCanvas to
      • a JFrame (the JFrame's content pane):
      • java.lang.IllegalArgumentException: adding a container to a container on a
        different GraphicsDevice
        at java.awt.Component.checkGD(Component.java:965)
        at java.awt.Container.addImpl(Container.java:1027)
        at java.awt.Container.add(Container.java:352)
        at JOGLWithNVidiaTwinView$2.run(JOGLWithNVidiaTwinView.java:88)
        at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:209)
        at java.awt.EventQueue.dispatchEvent(EventQueue.java:597)
        at
        java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:273)
        at
        java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:183)
        at
        java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:173)
        at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:168)
        at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:160)
        at java.awt.EventDispatchThread.run(EventDispatchThread.java:121)
        */
        public class JOGLWithNVidiaTwinView {

      /**

      • @param args Not used
        */
        public static void main(String[] args) {
        final XScreenDevice[] twinViewPair = XScreenDevice.findTwinViewScreens();
        if (twinViewPair == null) { System.out.println("No TwinView screen pair was found, exiting."); System.exit(1); }

      /*

      • Create a new Window on one head.
        */
        final JFrame frame = new
        JFrame(twinViewPair[0].device.getDefaultConfiguration());
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(200, 200);
        /*
      • Set the frame visible. We will wait until the window is opened before
      • moving it.
        */
        try { openAndWait(frame); System.out.println("Frame is on device with ID: " + frame.getGraphicsConfiguration().getDevice().getIDstring()); System.out.flush(); addCanvas(frame); System.out.println("Moving window to second device..."); System.out.flush(); /* * Get the bounds of the second device and move the frame there. */ final Rectangle secondDeviceBounds = twinViewPair[1].device.getDefaultConfiguration().getBounds(); moveTo(frame, secondDeviceBounds); System.out.println("Frame is on device with ID: " + frame.getGraphicsConfiguration().getDevice().getIDstring()); System.out.println("Adding GLCanvas..."); System.out.flush(); addCanvas(frame); }

        catch (InterruptedException ie)

        { System.out.println("Interrupted..."); }

        }

      /**

      • Creates and adds a new GLCanvas to the specified JFrame, blocking
      • until the operation is complete (do not call this from the Event Thread)
      • @param frame
      • @throws InterruptedException
        */
        static void addCanvas(final JFrame frame) throws InterruptedException {
        final boolean[] added = new boolean[] { false }

        ;
        final GLCanvas canvas = new GLCanvas();
        final ContainerListener listener = new ContainerAdapter() {

      @Override
      public void componentAdded(ContainerEvent e) {
      if (e.getComponent() == canvas) {
      synchronized (added)

      { added[0] = true; added.notifyAll(); }

      }
      }

      };
      EventQueue.invokeLater(new Runnable() {

      public void run() {
      final GLCanvas canvas = new GLCanvas();
      try

      { frame.getContentPane().add(canvas); }

      catch (IllegalArgumentException iae)

      { System.out.println("Caught exception while adding canvas: "); System.out.flush(); iae.printStackTrace(); }

      }
      });
      try {
      synchronized (added)

      { while (!added[0]) added.wait(); }

      } finally

      { frame.removeContainerListener(listener); }

      }

      /**

      • Moves the specified frame somewhere (top-left corner) of the specified
        rectangle, blocking until
      • the operation is complete (do not call this from the Event Thread).
      • @param frame
      • @param target
      • @throws InterruptedException
        */
        static void moveTo(final JFrame frame, final Rectangle target) throws
        InterruptedException {
        final boolean[] moved = new boolean[] { target.contains(frame.getX(), frame.getY(), frame.getWidth(), frame .getHeight()) }

        ;
        final ComponentListener listener = new ComponentAdapter() {

      public void componentMoved(ComponentEvent e) {
      synchronized (moved) {
      if (moved[0] = target.contains(frame.getX(), frame.getY(),
      frame.getWidth(), frame.getHeight()))

      { moved.notifyAll(); System.out.println("Moved to: " + frame.getX() + ", " + frame.getY()); }

      else

      { System.out.println("Moved somewhere unexpected: " + frame.getX() + ", " + frame.getY()); }

      }
      }
      };
      frame.addComponentListener(listener);
      EventQueue.invokeLater(new Runnable() {

      public void run()

      { frame.setLocation(target.x, target.y); }

      });
      try {
      synchronized (moved)

      { while (!moved[0]) moved.wait(); }

      } finally

      { frame.removeComponentListener(listener); }

      }

      /**

      • Sets the specified frame visible and waits for a corresponding WindowEvent.
      • Do not call from Event Thread.
      • @param frame
      • @throws InterruptedException
        */
        static void openAndWait(JFrame frame) throws InterruptedException {
        final boolean[] open = new boolean[] { frame.isVisible() }

        ;
        final WindowListener listener = new WindowAdapter() {

      public void windowOpened(WindowEvent e) {
      synchronized (open)

      { open[0] = true; open.notifyAll(); }

      }
      };
      frame.addWindowListener(listener);
      frame.setVisible(true);
      try {
      synchronized (open)

      { while (!open[0]) open.wait(); }

      } finally

      { frame.removeWindowListener(listener); }

      }

      /**

      • Class to extract some (Unix, or possible Linux-specific)
      • info from a GraphicsDevice instance's idString.
        */
        static class XScreenDevice {

      public final GraphicsDevice device;
      public final String hostName;
      public final String serverID;
      public final String displayID;

      public XScreenDevice(GraphicsDevice device) {
      this.device = device;
      String idStr = device.getIDstring();

      final int colonIdx = idStr.indexOf(':');
      if (colonIdx < 0)

      { throw new IllegalArgumentException("The specified device (id: " + idStr + ") does not appear to be an X screen device."); }

      hostName = idStr.substring(0, colonIdx);

      idStr = idStr.substring(colonIdx + 1);
      final int dotIdx = idStr.indexOf('.');
      if (dotIdx < 0)

      { displayID = ""; serverID = idStr; }

      else

      { serverID = idStr.substring(0, dotIdx); displayID = idStr.substring(dotIdx + 1); }

      }

      /**

      • Checks whether the specified device might be involved in a TwinView
      • configuration with this device. This means that the hostNames and
      • serverIDs must be identical, and the displayID and device must be
      • different.
      • @param device A non-null XScreenDevice.
      • @return <code>true</code> if the specified device is (probably) in
      • a TwinView configuration with this device, false otherwise.
        */
        public boolean isTwinViewComplement(XScreenDevice device) { return device.device != this.device && device.hostName.equals(this.hostName) && device.hostName.equals(this.hostName) && !device.displayID.equals(this.displayID); }

      public String toString()

      { return "X Screen Device -- id: " + device.getIDstring() + " (host=" + hostName + ", serverID=" + serverID + ", displayID=" + displayID + ")"; }

      /**

      • Identifies a pair of TwinView screens in the local graphics
      • environment. A TwinView pair (for the purposes of this method) has X
      • display IDs comprised of identical hostnames, followed by a colon,
      • identical server ids, followed by a '.', and unique display ids. For
      • example, ':0.0' and ':0.1' would be a pair.
      • @return A pair of XScreenDevice instances representing two distinct
      • GraphicsDevices in the local environment that are believed to
      • be a TwinView pair, or null if none are found.
        */
        public static XScreenDevice[] findTwinViewScreens() {
        final GraphicsEnvironment graphicsEnv =
        GraphicsEnvironment.getLocalGraphicsEnvironment();
        final GraphicsDevice[] screenDevices = graphicsEnv.getScreenDevices();
        if (screenDevices == null || screenDevices.length < 2) { /* * Not enough devices for a TwinView pair. */ return null; }

        for (int firstScreenIdx = 1; firstScreenIdx < screenDevices.length;
        firstScreenIdx++) {
        final XScreenDevice firstDevice;
        try

        { firstDevice = new XScreenDevice(screenDevices[firstScreenIdx]); System.out.println("Searching for TwinView complement to: " + firstDevice); }

        catch (IllegalArgumentException iae)

        { System.out.println("Device: " + screenDevices[firstScreenIdx].getIDstring() + " does not appear to be an X screen"); continue; }

      for (int secondScreenIdx = firstScreenIdx - 1; secondScreenIdx
      >= 0; secondScreenIdx--) {
      try {
      final XScreenDevice secondDevice = new
      XScreenDevice(screenDevices[secondScreenIdx]);
      System.out.println("\tTesting: " + secondDevice);
      if (firstDevice.isTwinViewComplement(secondDevice)) {
      System.out.println("TwinView Pair Identified!");
      return new XScreenDevice[]

      { firstDevice, secondDevice }

      ;
      }
      } catch (IllegalArgumentException iae)

      { /* * Already warned above... */ }

      }
      }
      /*

      • None found.
        */
        return null;
        }
        }
        }

        Activity

        Hide
        moorej added a comment -

        Created an attachment (id=105)
        Demo that illustrates TwinView error

        Show
        moorej added a comment - Created an attachment (id=105) Demo that illustrates TwinView error
        Hide
        krisher added a comment -

        Created an attachment (id=106)
        Proposed patch to GLCanvas to choose a visual as late as possible, seems to fix checkGD issues on Linux/Xorg/Xinerama.

        Show
        krisher added a comment - Created an attachment (id=106) Proposed patch to GLCanvas to choose a visual as late as possible, seems to fix checkGD issues on Linux/Xorg/Xinerama.
        Hide
        kbr added a comment -

        Thanks for the patch and sorry for not looking into it until now. This fix is
        excellent, basically the best that can be done under the circumstances. Fix has
        been checked in and will be present in tomorrow's 1.1.1-rc6 build.

        Show
        kbr added a comment - Thanks for the patch and sorry for not looking into it until now. This fix is excellent, basically the best that can be done under the circumstances. Fix has been checked in and will be present in tomorrow's 1.1.1-rc6 build.
        Hide
        kbr added a comment -
            • Issue 241 has been marked as a duplicate of this issue. ***
        Show
        kbr added a comment - Issue 241 has been marked as a duplicate of this issue. ***

          People

          • Assignee:
            jogl-issues
            Reporter:
            moorej
          • Votes:
            0 Vote for this issue
            Watchers:
            0 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved: