VTK/MultiPass Rendering With IceT
OverView
Since VTK 5.6, a multi-pass rendering framework was added to support rendering techniques that require multiple passes such as rendering shadows using shadow maps. Recently we extended the framework to enable use of IceT (Image Composition Engine for Tiles) for compositing. This makes it possible to write Parallel VTK code that renders on tile-displays or client-server configurations while using multi-pass rendering framework.
This document discusses the various classes involved and some example implementations added to the VTK/ParaView repository.
VTK or ParaView?
At the time of writing of this document the IceT source is included in ParaView and not VTK. Hence some IceT specific classes are in ParaView repository. However, we have plans to move IceT to VTK itself. Once that happens, these classes will move to VTK as well and one would have have to bring in ParaView for writing VTK-based examples using IceT.
All examples developed don't rely on any ParaView code except to bring in IceT libraries.
Things you should know before you start writing multi-process code
This article is of interest only to those who are writing parallel rendering code. All the onus of creating sensible visualization as well render-pass pipelines on all processes lies on the developer. We provide a collection of classes that aid in ensuring the that windows/renderers/cameras are synchronized among all processes. But the developers has to employ these classes and set them up correctly.
Simplest Case: Render a Sphere in Parallel
Here's a simple example that renders a distributed sphere in parallel using IceT.
<source lang="cpp"> // A simple example that demonstrate how to use the vtkIceTCompositePass and // supporting classes to render a sphere in parallel. // This only uses the minimal set of functionality and hence does not support // opacity < 1.0.
- include "vtkActor.h"
- include "vtkCamera.h"
- include "vtkCameraPass.h"
- include "vtkIceTCompositePass.h"
- include "vtkLightsPass.h"
- include "vtkMPIController.h"
- include "vtkOpaquePass.h"
- include "vtkPieceScalars.h"
- include "vtkPolyDataMapper.h"
- include "vtkPSphereSource.h"
- include "vtkRenderer.h"
- include "vtkRenderPassCollection.h"
- include "vtkRenderWindow.h"
- include "vtkRenderWindowInteractor.h"
- include "vtkSequencePass.h"
- include "vtkSmartPointer.h"
- include "vtkSynchronizedRenderers.h"
- include "vtkSynchronizedRenderWindows.h"
int main(int argc, char**argv) {
//--------------------------------------------------------------------------- // Initialize MPI.
vtkMPIController* controller = vtkMPIController::New(); controller->Initialize(&argc, &argv); vtkMultiProcessController::SetGlobalController(controller);
// Get information about the group of processes involved. int my_id = controller->GetLocalProcessId(); int num_procs = controller->GetNumberOfProcesses();
//--------------------------------------------------------------------------- // Create Visualization Pipeline. // This code is common to all processes. vtkSmartPointer<vtkPSphereSource> sphere = vtkSmartPointer<vtkPSphereSource>::New(); sphere->SetThetaResolution(50); sphere->SetPhiResolution(50);
// Gives separate colors for each process. Just makes it easier to see how the // data is distributed among processes. vtkSmartPointer<vtkPieceScalars> piecescalars = vtkSmartPointer<vtkPieceScalars>::New(); piecescalars->SetInputConnection(sphere->GetOutputPort()); piecescalars->SetScalarModeToCellData();
vtkSmartPointer<vtkPolyDataMapper> mapper = vtkSmartPointer<vtkPolyDataMapper>::New(); mapper->SetInputConnection(piecescalars->GetOutputPort()); mapper->SetScalarModeToUseCellFieldData(); mapper->SelectColorArray("Piece"); mapper->SetScalarRange(0, num_procs-1); // This sets up the piece-request. This tells vtkPSphereSource to only // generate part of the data on this processes. mapper->SetPiece(my_id); mapper->SetNumberOfPieces(num_procs);
vtkSmartPointer<vtkActor> actor = vtkSmartPointer<vtkActor>::New(); actor->SetMapper(mapper);
vtkSmartPointer<vtkRenderer> renderer = vtkSmartPointer<vtkRenderer>::New(); renderer->AddActor(actor);
vtkSmartPointer<vtkRenderWindow> renWin = vtkSmartPointer<vtkRenderWindow>::New(); renWin->AddRenderer(renderer);
//--------------------------------------------------------------------------- // Setup the render passes. This is just a very small subset of necessary // render passes needed to render a opaque sphere. vtkSmartPointer<vtkCameraPass> cameraP = vtkSmartPointer<vtkCameraPass>::New(); vtkSmartPointer<vtkSequencePass> seq = vtkSmartPointer<vtkSequencePass>::New(); vtkSmartPointer<vtkOpaquePass> opaque = vtkSmartPointer<vtkOpaquePass>::New(); vtkSmartPointer<vtkLightsPass> lights = vtkSmartPointer<vtkLightsPass>::New(); vtkSmartPointer<vtkRenderPassCollection> passes = vtkSmartPointer<vtkRenderPassCollection>::New(); passes->AddItem(lights); passes->AddItem(opaque); seq->SetPasses(passes);
// Each processes only has part of the data, so each process will render only // part of the data. To ensure that root node gets a composited result (or in // case of tile-display mode all nodes show part of tile), we use // vtkIceTCompositePass. vtkSmartPointer<vtkIceTCompositePass> iceTPass = vtkSmartPointer<vtkIceTCompositePass>::New(); iceTPass->SetController(controller);
// this is the pass IceT is going to use to render the geometry. iceTPass->SetRenderPass(seq);
// insert the iceT pass into the pipeline. cameraP->SetDelegatePass(iceTPass); renderer->SetPass(cameraP);
//--------------------------------------------------------------------------- // In parallel configurations, typically one node acts as the driver i.e. the // node where the user interacts with the window e.g. mouse interactions, // resizing windows etc. Typically that's the root-node. // To ensure that the window parameters get propagated to all processes from // the root node, we use the vtkSynchronizedRenderWindows. vtkSmartPointer<vtkSynchronizedRenderWindows> syncWindows = vtkSmartPointer<vtkSynchronizedRenderWindows>::New(); syncWindows->SetRenderWindow(renWin); syncWindows->SetParallelController(controller);
// Since there could be multiple render windows that could be synced // separately, to identify the windows uniquely among all processes, we need // to give each vtkSynchronizedRenderWindows a unique id that's consistent // across all the processes. syncWindows->SetIdentifier(231);
// Now we need to ensure that the render is synchronized as well. This is // essential to ensure all processes have the same camera orientation etc. // This is done using the vtkSynchronizedRenderers class. vtkSmartPointer<vtkSynchronizedRenderers> syncRenderers = vtkSmartPointer<vtkSynchronizedRenderers>::New(); syncRenderers->SetRenderer(renderer); syncRenderers->SetParallelController(controller);
//--------------------------------------------------------------------------- // Now start the event loop on the root node, on the satellites, we start the // vtkMultiProcessController::ProcessRMIs() so those processes start listening // to commands from the root-node.
if (my_id==0) { vtkSmartPointer<vtkRenderWindowInteractor> iren = vtkSmartPointer<vtkRenderWindowInteractor>::New(); iren->SetRenderWindow(renWin); iren->Start();
controller->TriggerBreakRMIs(); controller->Barrier(); } else { controller->ProcessRMIs(); controller->Barrier(); }
controller->Finalize(); vtkMultiProcessController::SetGlobalController(NULL); controller->Delete(); return 0;
} </source>
vtkIceTCompositePass
The above example illustrates how to use vtkIceTCompositePass. vtkIceCompositePass a vtkRenderPass subclass that uses IceT for compositing. When instantiated across different processes, it ensures that a composite result is available in the active render buffer on the root node (or the tile-result on all node in tile-display mode). If not in tile-display mode, then the active buffer on that node ends up with the result of the rendering of the data available on that processes, which may be partial depending on your pipeline.
vtkIceCompositePass has API to indicate if we are to treat the setup as a tile-display i.e. treating all nodes as if they form a large display and then only show the tile from this large display that corresponds to this process.
If data is replicated on all nodes, you may consider setting vtkIceCompositePass::DataReplicatedOnAllProcesses to true, which avoids unnecessary compositing.
Whenever translucent geometry or volume rendering is involved, one needs to ensure that a KdTree which can be used to generate an order for ordered-compositing is provided. This may require that you redistribute that data so that a KdTree can be generated. You may want to look at vtkDistributedDataFilter for redistributing data.
vtkIceCompositePass works on active frame buffer, just like any other vtkRenderPass. Hence, if you want apply additional passes such as say Sobel edge detection or Gaussian blur, you can add the vtkIceCompositePass as the delegate pass for vtkSobelGradientMagnitudePass or vtkGaussianBlurPass. Of course, except in tile-display mode, all nodes execept the root will we processing partial results (if data is distributed).
vtkSynchronizedRenderWindows: Synchronizing Render Windows
When dealing with parallel rendering, one needs to ensure that all render windows have the same size. Also typically the root node acts as the driver, in that case, we need to ensure that every time the root node renders, all the satellites also start rendering. All this is managed by vtkSynchronizedRenderWindows. In past, on used vtkParallelRenderManager to achieve the same effect. However vtkParallelRenderManager also synchronized the renderers and only supported 1 instance at a time.
Multiple instances of vtkSynchronizedRenderWindows can be used to synchronize multiple render windows, however each one should be assigned a unique id that's same on all processes.
vtkSynchronizedRenderers: Synchronizing Renderers
vtkSynchronizedRenderers is similar to vtkSynchronizedRenderWindows except that it synchorizes the renderers. Each synchronized renderer needs its own vtkSynchronizedRenderer. This does not do any compositing or any such thing. All it does is ensure that all the renders on the satellites have same camera parameters/viewport etc. as the root node.
Tests/Examples
Refer to the following tests as examples for how to use these classes effectively.
- ${ParaViewSource}/Servers/Filters/Testing/Cxx/TestSimpleIceTCompositePass.cxx
- The simple example reproduced on this page
- ${ParaViewSource}/Servers/Filters/Testing/Cxx/TestIceTCompositePass.cxx
- An exhaustive example that illustrates using sobel/blur/ordered-compositing/depth peeling with IceTComposite Pass. This example takes is a plethora of command line options (use --help to display them) than can be combined to exercise various components of the test.
- ${ParaViewSource}/Servers/Filters/Testing/Cxx/TestIceTShadowMapPass.cxx
- Illustrates using vtkIceTComposite pass in combination with complex render passes such as the ShadowMap render pass.
Migration to VTK
Currently VTK-ARB is discussing changes to VTK directory structure making it more modular. As a part of this restructuring, IceT components will be moved to VTK. Until then on has to depend on ParaView for IceT.