AVT Overview

High level overview

A notional AVT network. Note the arrow out of V2 is wrong and I'm not going to redo the diagram.

AVT stands for the "Analysis and Visualization Toolkit". It is an implementation of a data flow network design. Its base types are data objects and components (sometimes called process objects). The components can be sources, sinks, or filters. Sources have data object outputs, sinks have data object inputs, and filters have both an input and an output. A pipeline has a source (typically a file reader) followed by many filters followed by a sink (typically a rendering engine). Pipelines are demand driven, meaning that data is acquired through a pull operation. The pull is started with the sink. This forces the sink to generate an update request that propagates up the pipeline through the filters and ends at the source. Once the source is reached, the source generates the requested data, and then execute phases propagate down the pipeline until the sink is reached. VisIt's data flow network design also includes a contract which travels up the pipeline on update requests. This contract allows components to communicate with other pipeline components upstream.

Where is AVT used

VisIt's engine makes the most heavy use of the AVT library. The engine's job, on the whole, is to assemble AVT networks and execute them. The engine is approximately ten thousand lines of code, compared with approximately three hundred thousand lines of code in AVT. It should be noted that the majority of parallel rendering is done in the engine, however, and not AVT. (The actual rendering modules are located in AVT, but the algorithms for parallelization are at the engine level.)

VisIt's viewer makes some use of the AVT library. Each AVT plot contains a rendering module. That module needs to be located on the viewer for the case of non-scalable rendering. Also, the VisWindow class, located in the avt directory and sometimes thought of as part of AVT is used in the viewer.

VisIt's mdserver also makes some use of the AVT library, with its use of avtDatabase's and avtFileFormat's from database plugins.

The primary AVT data flow modules

Data Objects

  • avtDataObject: an abstract type that represents a data object
    • avtDataset: a concrete, derived type of avtDataObject that contains many vtkDataSets
    • avtImage: a concrete, derived type of avtDataObject that contains an image
    • avtSamplePoints: a concrete, derived type of avtDataObject that stores sample points
  • avtDataObjectInformation: one of these is contained by every avtDataObject. It contains two data members:
    1. avtDataObjectAttributes: contains information like topological dimension, spatial dimension, extents, etc
    2. avtDataValidity: contains error information and information about whether meta-data is still valid.

These modules are located in /src/avt/Pipeline/Data.

Sources

  • avtSource: an abstract type that is a source of data. Its defining characteristic is that it has an output.
    • avtOriginatingSource: an abstract type that understands it will be the top of a pipeline.
    • avtDatasetSource: a concrete, derived of avtSource. It knows that its output is an avtDataset.
    • avtImageSource, avtSamplePointsSource: see avtDatasetSource
    • avtOriginatingDatasetSource: a abstract type, inheriting multiply from avtOriginatingSource and avtDatasetSource (creating diamond shaped inheritance from avtSource). This is the top of the pipeline with an avtDataset output.
    • avtSourceFromDatabase: a concrete, derived of avtOriginatingDatasetSource. It is an originating dataset source that has a connection to a database and can read data from that database.

These modules are located in /src/avt/Pipeline/Sources.

Sinks

  • avtSink: an abstract type that is a sink for data. Its defining characteristic is that it has an input.
    • avtTerminatingSink: an abstract type that understands it will be the bottom of a pipeline.
    • avtDatasetSink: a concrete, derived of avtSink. It knows that its input is an avtDataset.
    • avtImageSink, avtSamplePointsSink: see avtDatasetSink
    • avtTerminatingDatasetSink: a concrete type, inheriting multiply from avtOriginatingSink and avtDatasetSink (creating diamond shaped inheritance from avtSink). This is the bottom of the pipeline with an avtDataset input.

These modules are located in /src/avt/Pipeline/Sinks.

Filters

  • avtFilter: an abstract type that inherits multiply from avtSource and avtSink, meaning it has an input and an output. It defines how to propagate an update request up the pipeline and contains an Execute method.
    • avtDatasetToDataObject: an abstract type inheriting multiply from avtFilter and avtDatasetSource. This class understands its input will be an avtDataset.
    • avtDataObjectToDatasetFilter: an abstract type inheriting multiply from avtFilter and avtDatasetSink. This class understands its output will be an avtDataset.
    • avtDatasetToDatasetFilter: an abstract type inheriting multiply from avtDatasetToDataObjectFilter and avtDataObjectToDatasetFilter. This class understands that both its input and output will be avtDatasets.
      • avtStreamer / avtDataTreeStreamer : concrete derived types of avtDatasetToDatasetFilter that provide infrastructure for operating on avtDatasets (see below)
    • avtDatasetToImageFilter: an abstract type inheriting multiply from avtDatasetToDataObjectFilter and avtDataObjectToImageFilter. This class understands that its input will an avtDataset and that its output will be an avtImage. This is the base class for a volume rendering filter.
    • avtPluginFilter: an abstract type inheriting from avtFilter. This provides the interface necessary for setting the attributes for a plugin operator.

These modules are located in /src/avt/Pipeline/AbstractFilters.

Diagram of inheritance hierarchy for sources, sinks, and filters

The image below shows the inheritance hierarchy for making a filter with input of type DO2 and output of type DO1. When dealing an abstract data object, the term DO is used. As unwieldy as this inheritance tree looks, it works because:

  1. The total number of data objects is small.
  2. Implementing this hierarchy takes very few lines of code (just declaring classes and where they inherit from).
    • There is a script that does this automatically here.
  3. It allows for each method to be declared at the exact correct level.
    • For example, if you know that any filter that has an avtDataset input should perform activity XYZ when event ABC occurs, you can reimplement a virtual method corresponding to ABC in the class avtDatasetToDataObjectFilter to do XYZ.

Avt inheritance hierarchy.jpg

How pipeline execution works

  • You initiate the execution by calling avtOriginatingSink::Execute() on an instance of an originating sink.
    • An argument to this method will be an avtContract.
  • avtDataObjects understand what their source is (in terms of an avtSource). When an avtDataObject is asked to Update, it simply goes to its avtSource and calls its Update method.
  • avtSource defines a pure virtual method for Update. It is only re-defined twice. Once by avtFilter and once by avtOriginatingSource.
    • avtFilter::Update propagates the update up the pipeline (by calling Update on the input it inherits through avtSink). This call ensures everything upstream is up-to-date. Afterwards, it calls Execute().
    • avtOriginatingSource::Update accepts the Update request, consults a load balancer, and then calls methods to actually fetch data.

Connection to VTK

AVT is a complete implementation of a data flow network scheme that does not directly depend on VTK. avtDataset, however, is a derived type of avtDataObject that utilizes VTK. And all filters that inherit from avtDatasetToDataObjectFilter or avtDataObjectToDatasetFilter understand and use VTK.

avtDataset

avtDataset is a concrete derived type of avtDataObject. Its principle data member is an avtDataTree. avtDataTree is an object that contains either an array of children (internal nodes) or a single vtkDataSet (leaf nodes). [More correctly, the avtDataTree's leaf nodes contain an avtDataRepresentation, which is a wrapper around a single vtkDataSet. The advantage of avtDataRepresentation is that it can store a vtkDataSet as a byte stream.]

It is entirely possible to walk through any avtDataTree. However, convenience routines have been set up to aid in this process:

  • If you want to write a filter that executes one time for each vtkDataSet, inherit from:
    1. avtStreamer if the output for a given vtkDataSet will be a single vtkDataSet. You will re-inherit the method ExecuteData
    2. avtDataTreeStreamer if the output for a given vtkDataSet will be multiple vtkDataSets. You will re-inherit the method ExecuteTree.
  • If you want to iterate over an avtDataTree, and call a function for each vtkDataSet in the tree, you can:
    1. See if an existing method exists for this in /src/avt/Pipeline/Data/avtCommonDataFunctions.h
    2. Write a function that meets the TraverseFunc interface and use the method avtDataTree::Traverse()
    3. Write a recursive function for iterating over the data tree one node at a time.
    4. Call the method avtDataTree::GetAllLeaves(), which returns an array of pointers to vtkDataSets.

How a real pipeline works

For 99% of the time, the following is true:

  • The top of the pipeline will be an avtSourceFromDatabase, which in turn has a connection to an avtGenericDatabase, which in turn has a connection to the avtFileFormat's implemented by our database plugins.
  • The first filter in the pipeline is the avtExpressionEvaluatorFilter, or EEF for short.
    • This filter will examine the contract during an update. If it detects requests for variables that are expressions, it will remove those requests and replace the requests with requests for the database variables they depend on.
    • During the Execute phase, the EEF creates a sub-pipeline to create all the requested expressions.
  • The next filters are from any operators.
  • The plot contributes many filters:
    1. Filters from virtual method "avtPlot::ApplyOperators"
    2. Filters from virtual method "avtPlot::ApplyRenderingTransformation"
    3. "Rendering filters", which help to reduce memory footprint and prepare for rendering
      1. The avtGhostZoneAndFacelistFilter, which does ghost data removal and face list extraction. The order of these two operations depends on pipeline properties.
      2. The avtCondenseDatasetFilter, which discards unneeded data.
        • The normal example of this is that a vtkPolyData has a point list and that point list may have points that are not incident to any triangles. (These points can be removed.)
      3. The avtVertexNormalsFilter, which creates normals.
      4. The avtCompactTreeFilter, which takes an avtDataTree and appends all of the data sets into one big data set (when possible).
        • The motivation here is:
          1. There is non-trivial memory overhead for each vtkDataSet
          2. If you leave them separate, they each get their own vtkMapper on the viewer, which bloats memory and also causes OpenGL state thrashing.
      5. avtCurrentExtentsFilter, which calculates extents.

example network

Timeline Overview

At a high level:

  1. A user opens a file, which causes the mdserver to open an avtFileFormat and get various information.
  2. Later, when the user makes plots and add operators, the engine responds by constructing an AVT network.
  3. When the user clicks draw, the AVT network is executed.
    • In non-scalable rendering, the resulting surface is transferred to the viewer and rendered locally. The rendering is done using an avtPlot's "mapper" module being called in the context of a VisWindow's visualization window.
    • In scalable rendering, the surface is rendered in parallel, and the engine transfers an image back to the viewer.
  4. Subsequent actions, like queries and picks, cause the engine to connect new sinks to that AVT network.

The timeline is described in more detail here.


Links

Other suggested topics

  • Data
  • Contracts
  • AMR strategy
  • Filters
  • Queries
  • Expressions
  • Plotter
  • Viswindow