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)

<source lang="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);
           }
       });
   }

} </source>

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.

We need to implement a worker that is capable of producing actors. In the sample code we produce sphere actors with shrunk polygons in order to have something interesting that takes a bit of time to create. <source lang="java">

   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;
       }
   }

</source>

A separate worker's job is to add actors to the renderer when ready. <source lang="java">

   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();
       }
   }

</source>

In our initialization 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.

<source lang="java">

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

</source>

We need to set up our completion service. <source lang="java">

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

</source>

Next a setupWorkers() method starts a thread which invokes the code to add actors to the renderer whenever our executor completion service has a new actor available. Note that the code adding the actors to the renderer must be done on the event thread using SwingUtilities.invokeAndWait(), since that is where the renderer object was created and lives. <source lang="java">

   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();
   }

</source>

We'll also create a timer which every second renders the scene and takes out a sphere actor. This code also manually run the garbage collector using vtkObject.JAVA_OBJECT_MANAGER.gc() if the runGC checkbox is selected. Note that timers are scheduled on the Swing event thread, which is why we are allowed to manipulate the renderer and its actors here.

<source lang="java">

       // Update GC info into the UI every second.
       // Reset camera each of the first 10 seconds.
       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();

</source> <source lang="java">

   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());
           }
       });
   }

</source> <source lang="java">

   public void startWorking() {
       for (int i = 0; i < NUMBER_OF_PIPLINE_TO_BUILD; i++) {
           exec.submit(new PipelineBuilder());
       }
   }

</source> <source lang="java">

   // -----------------------------------------------------------------
   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();
           }
       });
   }

</source>

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.