VTK/Java Wrapping

From KitwarePublic
Jump to navigationJump to search

Configuration

You basically just need to turn VTK_WRAP_JAVA on in CMake and build.

Bartlomiej Wilkowski has created a nice tutorial of configuring Java wrapping with VTK.

Mac (Snow Leopard)

To build a sample application provided in VTK against your VTK build directory (with an installed VTK replace "bin" with "lib"):

$ export DYLD_LIBRARY_PATH=$DYLD_LIBRARY_PATH:your_vtk_build_dir/bin
$ javac -cp your_vtk_build_dir/bin/vtk.jar your_vtk_source_dir/Wrapping/Java/vtk/sample/Demo.java
$ java -cp your_vtk_build_dir/bin/vtk.jar:your_vtk_source_dir/Wrapping/Java vtk.sample.Demo

Sample Code (from VTK/Wrapping/Java/vtk/sample/SimpleVTK.java)

/**
 * An application that displays a 3D cone. A button allows you to close the
 * application.
 */
public class SimpleVTK extends JPanel implements ActionListener {
    private static final long serialVersionUID = 1L;
    private vtkPanel renWin;
    private JButton exitButton;

    // -----------------------------------------------------------------
    // Load VTK library and print which library was not properly loaded
    static {
        if (!vtkNativeLibrary.LoadAllNativeLibraries()) {
            for (vtkNativeLibrary lib : vtkNativeLibrary.values()) {
                if (!lib.IsLoaded()) {
                    System.out.println(lib.GetLibraryName() + " not loaded");
                }
            }
        }
        vtkNativeLibrary.DisableOutputWindow(null);
    }

    // -----------------------------------------------------------------
    public SimpleVTK() {
        super(new BorderLayout());

        // build VTK Pipeline
        vtkConeSource cone = new vtkConeSource();
        cone.SetResolution(8);

        vtkPolyDataMapper coneMapper = new vtkPolyDataMapper();
        coneMapper.SetInputConnection(cone.GetOutputPort());

        vtkActor coneActor = new vtkActor();
        coneActor.SetMapper(coneMapper);

        renWin = new vtkPanel();
        renWin.GetRenderer().AddActor(coneActor);

        // Add Java UI components
        exitButton = new JButton("Exit");
        exitButton.addActionListener(this);

        add(renWin, BorderLayout.CENTER);
        add(exitButton, BorderLayout.SOUTH);
    }

    /** An ActionListener that listens to the button. */
    public void actionPerformed(ActionEvent e) {
        if (e.getSource().equals(exitButton)) {
            System.exit(0);
        }
    }

    public static void main(String s[]) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame("SimpleVTK");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.getContentPane().setLayout(new BorderLayout());
                frame.getContentPane().add(new SimpleVTK(), BorderLayout.CENTER);
                frame.setSize(400, 400);
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }
}

Some key points from this code to note:

  • vtkNativeLibrary.LoadAllNativeLibraries() is required to load the dynamic libraries from VTK's C++ core. This call must happen before any VTK code executes, which is why it is put in a static block in our application class.
  • vtkNativeLibrary.DisableOutputWindow(null) simply hides any debugging information that may otherwise pop up in a dialog box when any warnings are reached. This is good to call when releasing an application.
  • SwingUtilities.invokeLater(...) is called because technically all GUI code, including setting up and using a VTK render window, should happen in the Swing event thread.

Threading Sample Code (from VTK/Wrapping/Java/vtk/sample/Demo.java)

In this demo, we want to illustrate the correct way to perform VTK tasks on separate threads in Java. The first thing to note is that VTK is inherently NOT thread-safe, which immediately rules out several possible use cases. Calling methods on the same VTK objects across threads, even if they seem to be read-only, should be avoided. The safest approach is to "hand off" objects from one thread to another, so one thread is completely done with an object before another thread begins manipulating it. Reclaiming memory for VTK objects is particularly tricky to perform across threads, as deleting a single VTK object may potentially cause the entirety of VTK objects to be modified. While we expose the Delete() method to explicitly delete VTK objects, if you are using VTK objects in multiple threads this is discouraged. VTK provides a special garbage collector for VTK objects in the Java layer that may be run periodically is memory reclaiming is needed.

For this example, we will have a checkbox for turning on and off the VTK garbage collection while an application is running. The application creates new actors using a separate processing thread, which are then added dynamically to the VTK renderer. This enables data to be loaded and processed without causing lags in the frame rate of the interactive 3D view.

Our demo class will have a completion service which creates actors.

public class Demo extends JPanel {
    private final CompletionService<vtkActor> exec;

We need to implement a worker that is capable of producing actors. In the sample code we produce sphere actors with shrunk polygons in otder to have something interesting.

    // -----------------------------------------------------------------
    public static class PipelineBuilder implements Callable<vtkActor> {
        private vtkActor actor;
        ...
        @Override
        public vtkActor call() throws Exception {
            // Set up a new actor
            actor = new vtkActor();
            ...
            // Wait some time for other thread to work
            Thread.sleep((long) (Math.random() * 500));
            // Return
            return actor;
        }
    }

A separate worker's job is to add actors to the renderer when ready.

    public static class AddActorRunnable implements Runnable {
        private vtkActor actorToAdd;
        private vtkRenderer renderer;
        private vtkPanel panel;

        void setRenderer(vtkPanel panel) {
            this.renderer = panel.GetRenderer();
            this.panel = panel;
        }

        void setActor(vtkActor a) {
            this.actorToAdd = a;
        }

        @Override
        public void run() {
            this.renderer.AddActor(this.actorToAdd);
            this.panel.Render();
        }
    }

In our constructor code, we need to set up several things. First, two checkboxes toggle VTK's garbage collection and the debug mode. Since VTK is a C++ library, it has its own mechanism for ensuring that unused objects are deleted from memory. Many threading issues can be avoided by simply turning off VTK garbage collection.

        runGC = new JCheckBox("Enable GC", false);
        debugMode = new JCheckBox("Debug mode", false);

We need to set up our completion service.

        exec = new ExecutorCompletionService<vtkActor>(Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()));


        // Init app
        // this.setupGC(); // We use a Swing timer that show the result in UI
        // instead..
        this.setupWorkers();
        // Update GC info into the UI every seconds
        // Reset camera each seconds the first 10 ones
        this.nbSeconds = 0;
        new Timer(1000, new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                if (nbSeconds++ < 10) {
                    panel3d.resetCamera();
                }
                vtkRenderer renderer = panel3d.GetRenderer();
                if (renderer.GetNumberOfPropsRendered() > 1) {
                    renderer.RemoveActor(renderer.GetActors().GetLastProp());
                }

                // Run GC in local thread (EDT)
                if (runGC.isSelected()) {
                    vtkReferenceInformation info = vtkObject.JAVA_OBJECT_MANAGER.gc(debugMode.isSelected());
                    if (debugMode.isSelected()) {
                        System.out.println(info.listKeptReferenceToString());
                        System.out.println(info.listRemovedReferenceToString());
                    }
                    gcStatus.setText(info.toString());
                } else {
                    gcStatus.setText("");
                }

                panel3d.Render();
            }
        }).start();
    private void setupGC() {
        // Setup GC to run every 1 second in EDT
        vtkObject.JAVA_OBJECT_MANAGER.getAutoGarbageCollector().SetScheduleTime(1, TimeUnit.SECONDS);

        // Start/Stop the GC based on the checkbox
        runGC.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent arg0) {
                vtkObject.JAVA_OBJECT_MANAGER.getAutoGarbageCollector().SetAutoGarbageCollection(runGC.isSelected());
            }
        });

        // Change GC mode based on the checkbox
        debugMode.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent arg0) {
                vtkObject.JAVA_OBJECT_MANAGER.getAutoGarbageCollector().SetDebug(debugMode.isSelected());
            }
        });
    }
    private void setupWorkers() {
        // Add actor thread: Consume the working queue and add the actor into
        // the render inside the EDT thread
        final AddActorRunnable adderRunnable = new AddActorRunnable();
        adderRunnable.setRenderer(panel3d);
        new Thread() {
            public void run() {
                for (int i = 0; i < NUMBER_OF_PIPLINE_TO_BUILD; i++) {
                    try {
                        adderRunnable.setActor(exec.take().get());
                        SwingUtilities.invokeAndWait(adderRunnable);
                        panel3d.repaint();
                    } catch (InterruptedException e) {
                        return;
                    } catch (ExecutionException e) {
                        e.printStackTrace();
                    } catch (InvocationTargetException e) {
                        e.printStackTrace();
                    }
                }
            };
        }.start();
    }
    public void startWorking() {
        for (int i = 0; i < NUMBER_OF_PIPLINE_TO_BUILD; i++) {
            exec.submit(new PipelineBuilder());
        }
    }
    // -----------------------------------------------------------------
    public static void main(String[] args) throws InterruptedException, InvocationTargetException {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                Demo app = new Demo();

                JFrame f = new JFrame("Concurrency test");
                f.getContentPane().setLayout(new BorderLayout());
                f.getContentPane().add(app, BorderLayout.CENTER);
                f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                f.setSize(400, 400);
                f.setVisible(true);
                f.validate();

                app.startWorking();
            }
        });
    }

Java Wrapper Refactoring (Oct 8, 2007)

There were a few problems with the old Java wrappers. One was that, as you said, objects were being deleted before they were supposed to. We hacked in a fix at one point about a year ago which basically made all VTK objects accessed from Java stay around forever, but this was not acceptable either.

Ref:

The other major concern was that the map from Java objects to VTK objects was in the C++ JNI layer, and while we tried to keep this map synchronized with a mutex, race conditions could still occur because other Java threads could advance while the JNI layer was being called (a thread could access a C++ object just as it is being garbage-collected and deleted). There does not seem to be a way to atomically call a JNI method, or ensure the collector doesn't run while a method is called. This second issue forced us to rethink how the map is done, and the solution was to keep the map in Java instead of C++. But we didn't want this Java map to prohibit objects from being garbage collected. Fortunately, Java has a WeakReference class for just this type of purpose. When accessed, the reference will either be valid or null depending on whether it has been garbage-collected.

http://java.sun.com/j2se/1.4.2/docs/api/java/lang/ref/WeakReference.html

Thus, the wrapper code can lookup objects in this map when returning objects from methods, and if it is not there, or null, it creates a new Java object representing that C++ object.

A final issue was that we wanted a way to guarantee all C++ destructors are called before the program exits. The natural place to decrement the reference count of the C++ object is in finalize(), which works when things are garbage-collected, but Java does not guarantee that finalize will ever be called. So the method vtkGlobalJavaHash.DeleteAll() will plow through the remaining VTK objects and call Delete on them.