Hiding VTK-m filters in ParaView

Hi there!

As a part of a long term task to further integrate VTK-m into ParaView, we are trying to expose VTK-m filters in the ParaView UI not as independent filters (such as vtkmContour) but instead under the same name as its corresponding PV filter (such as Contour).

So far we have came up with the following strategy:

In VTK, we override VTK filters that has its corresponding in VTK-m using the vtkObjectFactory pattern

In PV, we changed the implementation of the vtkPV*Filters that have a corresponding filter in VTK-m to delegate into a vtk*Filter instead of its superclass. This allows us to create a vtkm or vtk filter using the convienient vtkObjectFactory::CreateInstance at the vtkPV*Filter at run-time.

In other words:

In the vtkPV*Filter.h

  ...
  vtk*Filter* impl;
};

In the vtkPV*Filter.cpp

vtkPV*Filter::vtkPV*Filter()
{
  // First we try to create the filter from vtkObjectFactory
  this->impl = static_cast<vtk*Filter*>(vtkObjectFactory::CreateInstance("vtk*Filter"));
  if (!this->impl)
  {
    // If it fails we default to standard vtk*Filter
    this->impl = vtk*Filter::New();
  }

Still this brings some issues as:

  1. What do we do with VTKm filters that do not have its corresponding in ParaView? A simple approach is to remove its “vtkm” prefix (Ex: vtkmExternalFaces → ExternalFaces).
  2. Where would be a good place to place a button/switch to toggle “using vtkm as a backend for filters”.

Also, I wonder how sound is the strategy that we came up, we are really looking forward feedback on original ideas to achieve our goals.

Vicente

One thing to note is that change shouldn’t prevent plugins already using factory mechanism to keep working. There is no reason it would do that, but to keep in mind.

Should this factory implementation be handled in the VTK layer instead? Perhaps the New method of the vtk filter could instantiate itself with the object factory.

vtkm*Filter vtk*Filter::New()
{
  vtkObject* obj = static_cast<vtk*Filter>(vtkObjectFactory::CreateInstance("vtk*Filter"));
  if (obj)
  {
    return static_cast<vtk*Filter>(obj);
  }
  else
  {
    return vtk*Filter::New();
  }
}

That way, the problem would be solved for other VTK applications, not just ParaView.

I guess the right way might be to create an abstract superclass shared among the “serial” and “VTK-m” versions. I’m not sure how necessary that is.

1 Like

Do the VTK-m filters have the same results as the VTK filter it represents? What if a user wants vtkmContour, but vtkOtherVtkmFilter in the same pipeline (say, due to memory limitations on their GPU if both are active at the same time)?

In principal, yes, the VTK-m filter should have the same results as the VTK filter. It should be transparent to the user. Of course, the two outputs might not be bitwise-equal. A contour could, for example, easily have the polygons in a different order or different decisions for ambiguous cases. But such changes can happen from one version of ParaView to the next, so I don’t think that would be an issue.

OK, just making sure :slight_smile: .

Should this factory implementation be handled in the VTK layer instead? Perhaps the New method of the vtk filter could instantiate itself with the object factory.

I really entertained this idea and I spent a good time figuring out how to approach this, I came up with the following scheme onto how to implement it:

  • Note that thanks to the vtkFactories vtk*FilterExtended does not depend onto vtkm*Filter at the implementation level,.

  • Also, instead of a Abstract class I added a subclass where this switch is done. This is mainly to avoid modifying the VTK filters hierarchy. It is basically a Decorator with vtkFactories

While this will indeed benefit other VTK applications, this approach requires to:

  • On ParaView, vtkPVFilters to subclass on this new vtkFiltersExtended (Not a big deal)
  • While the vtkFilterExtended does not include the vtkmFilter. At the module level, we do have to include as dependency the VTKm module in order for this whole factory thing to work (AFAIK).

I am hesitant to add those changes for VTK mainly since it will add a hard dependency onto VTKm.

I was actually thinking something more like this:

(pdf version)

You could rename the current filter (let’s call it vtkFooFilter so we don’t have wildcards) to, say, vtkFooFilterSerial (or something else appropriate). Then, create a new class with the original name (i.e. vtkFooFilter). This class has the same interface as the original filter, but it is an abstract class.

Per the rules of abstract classes in VTK, you should be able to call the New method, and the New method will use the vtkObjectFactory to create a concrete class. By default, the vtkFooFilterSerial will be registered with the vtkObjectFactory. However, if VTK-m is compiled, vtkmFooFilter can be registered instead.

Note that this means that any existing code that currently uses vtkFooFilter will continue to work as before. If VTK-m is not installed, when they call vtkFooFilter::New(), it will get vtkFooFilterSerial, which is the exact same implementation as before. However, if VTK-m is compiled, the code will get and use vtkmFooFilter.

Also note that this is exactly how the object factory is supposed to work.

The PV version of the filter (assuming we need a special PV version of the filter at all) simply needs to get a vtkFooFilter as before.

Note that the library needs linked to (and passed to vtk_module_autoinit) to work for compiled code (in general). Python will need to import the appropriate VTKm module in order to guarantee the factory implementation is registered. Note that where it is imported/instantiated doesn’t really matter, but it does need to happen.

This looks like a very sound design, however, my main concern is that in this design, In the module corresponding to vtkFooFilter (vtk.module) , don’t we have to add vtkm as a dependency? Because the New() method will be defined there? I am not 100% sure though.

Note that, I already understood this idea in the first comment and I shared it, but I tweaked it to accommodate this concern in the previous comment

This is actually somewhat similar to how it is currently implemented. The VTKm filters all inherit from the serial implementation so that they can call the superclass if the VTKm version fails for some reason. Using the factory method in the superclass and registering the VTKm subclasses, you would get the same end result. Ken’s design is cleaner on one hand but does not allow for delegating to superclass like the current implementation.

Yes, the delegation is an issue I didn’t address. I figured the VTK-m subclass would have a “has-a” link to the VTK subclass for the fallback. I didn’t put that in the diagram to hopefully make it less confusing.

One thing we might have missed in the discussion is whether it is a good idea to implement our own New method. Revering engineering the macros, in its implementation, it appears that it does what we are discussing here (if certain cmake options are set), but I can see that if in the future there are changes to those macros, we might be missing out those changes in the filters here discussed.

We could introduce another factory method, however, it will add reduce consistency in the code-base in the sense that the standard new method will not be enough to create certain filters.

Without the new method and without introducing a new factory method we do not really have a way on doing this switch in the VTK layer.

Here’s another slightly different approach. This allows for specialization based on input data types / element types etc. It does mean the vtkFooFilter will need to be involved in making the decision on which vtkBaseFooImpl subclass to create but that’s probably better since it allows for more informed decision making.

In this design, vtkFooFilter becomes the vtkAlgorithm subclass that implements the vtkAlgorithm API along with defining the public API for the filter. However, it uses a vtkBaseFooImpl abstract class to do the actual execution. This keeps us from having several different subclasses or variants of vtkFooFilter based on input type or other specific criteria. For example, we could have one vtkSurfaceFilter and different internal implementations based on input type, cell type etc. rather than having multiple versions. Same for Isocontour, for example, or Threshold – the list goes on. Thus, rather than having the users choose which filter to use for their type of data and environment, the decision is passed on to the VTK developer.

vtkFooFilter can expose API to choose a specific implementation, if necessary.

What this also allows us is to build composite filters with ease. For example, An IsoVolume filter wants to do a Threshold internally when computing isovolume for cell data. It can directly use the vtkIsoVolumeImpl. This avoids executing all the vtkAlgorithm passes on this internal filter – which can easily add up.


Furthermore, vtkBaseFooImpl should probably not use the simple New, instead support the following (or similar).

vtkBaseFooImpl* New(vtkInformation* params);

The params can be used to pass key-value pairs that help choose the implementation to use. Now the logic to choose best implementation can be in vtkBaseFooImpl itself. Now, ObjectFactory API should also be extended similarly to allow for customizing and extending this logic. The keys available can be defined in vtkBaseFooImpl itself, e.g. for the diagram shown, we can have the following:

class vtkBaseFooImpl : public vtkObject
{
public:
  // Choose a specific runtime.
  static vtkInformationStringKey* RUNTIME();

  // Indicates if data type.
  static vtkInformationIntegerKey* DATA_OBJECT_TYPE();

  // Some impls may want even more control, say they want to look at what
  // type cells are present, or for composite dataset, what types of individual datasets
  // are present. For those, we can even pass the input data object instead of just the type.
  static vtkInformationDataObjectKey* INPUT_DATA();

  ...
};

The vtkFooFilter will use these keys to populate a vtkInformation object in RequestData to create the right vtkBaseFooImpl subclass and then use it.

1 Like

Addressing @vbolea, custom New methods are possible, but I’m not sure why we would want them. The macros for abstract and concrete classes seem to do what we want. I don’t expect the object factory classes/macros to change. They were old when I started using VTK over 20 years ago. And if someone does change them, it is on that person to fix any classes that rely on its behavior.

As for @utkarsh.ayachit’s design, that sounds like positive value. But I note that this is really expanding the scope of the work. This design is no longer about “hiding VTK-m filters” but redesigning complex filters that have multiple implementations. Also, it’s a design that will have to be reimplemented for every filter, and it might be overkill for simple filters.

Addressing @vbolea, custom New methods are possible, but I’m not sure why we would want them. The macros for abstract and concrete classes seem to do what we wan

Yeah I was referring and evaluating the proposed idea of moving custom New methods to the vtkFooFilters in the example that you wrote earlier in this thread.

@utkarsh.ayachit’s vtkInformation passing echos of this issue to allow for more control over object factory selections (right now, you just hope that the one you want is first in the list). In light of enable/disable APIs for factories, any such mechanism should also have a way to influence within a scope.

However, given VTK’s propensity for global state (which is a problem in the context of threading) and no parameters to constructors (with configuration after-the-fact), any viable solution is going to end up being ugly I imagine.

Fair point. I’ll defer to you and others working on this to determine that. It may be worth exploring this option when the opportunity presents itself, though. Maybe we decide to take a serious look at DataSetSurface filter (and GeometryFilter and the ilk), we can explore this approach.