VTK/Java Wrapping: Difference between revisions
Line 97: | Line 97: | ||
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. | 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 | We need to implement a worker that is capable of producing actors. In the sample code we produce | ||
sphere actors with shrunk polygons in | sphere actors with shrunk polygons in order to have something interesting that takes a bit of time to create. | ||
<source lang="java"> | <source lang="java"> | ||
public static class PipelineBuilder implements Callable<vtkActor> { | public static class PipelineBuilder implements Callable<vtkActor> { | ||
private vtkActor actor; | private vtkActor actor; | ||
Line 148: | Line 141: | ||
</source> | </source> | ||
In our | 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"> | <source lang="java"> | ||
Line 160: | Line 153: | ||
</source> | </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"> | <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> | </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"> | <source lang="java"> | ||
// Update GC info into the UI every | // Update GC info into the UI every second. | ||
// Reset camera each | // Reset camera each of the first 10 seconds. | ||
this.nbSeconds = 0; | this.nbSeconds = 0; | ||
new Timer(1000, new ActionListener() { | new Timer(1000, new ActionListener() { | ||
Line 219: | Line 234: | ||
} | } | ||
}); | }); | ||
} | } | ||
</source> | </source> |
Revision as of 19:14, 19 August 2011
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.