diff --git a/docs/changelog/NewFitlerInterface.md b/docs/changelog/NewFitlerInterface.md new file mode 100644 index 000000000..d5e596245 --- /dev/null +++ b/docs/changelog/NewFitlerInterface.md @@ -0,0 +1,20 @@ +## New Filter Interface Design ## + +An overhaul of the Filter interface is undergoing. This refactoring effort will +address many problems we faced in the old design. The most important one is to +remove the requirement to compile every single Filter users with a Device Compiler. +This is addressed by removing C++ template (and CRTP) from Filter and is subclasses. +A new non-templated NewFilter class is added with many old templated public interface +removed. + +This new design also made Filter implementations thread-safe by default. Filter +implementations are encouraged to take advantage of the new design and removing +shared metatable states from their `DoExecute`, see Doxygen documentation in +NewFilter.h + +Filter implementations are also re-organized into submodules, with each submodule +in its own `vtkm/filter` subdirectory. User should update their code to include +the new header files, for example, `vtkm/filter/field_transform/GenerateIds.h`and +link to submodule library file, for example, `libvtkm_filter_field_transform.so`. +To maintain backward compatability, old `vtkm/filter/FooFilter.h` header files +can still be used but will be deprecated in release 2.0. diff --git a/vtkm/filter/CMakeLists.txt b/vtkm/filter/CMakeLists.txt index fc9829d56..bb78c89a3 100644 --- a/vtkm/filter/CMakeLists.txt +++ b/vtkm/filter/CMakeLists.txt @@ -191,7 +191,6 @@ set(extra_sources_device ${ClipWithFieldInstantiations} ${ClipWithImplicitFunctionInstantiations} ExternalFaces.cxx - GenerateIds.cxx VectorMagnitude.cxx particleadvection/Messenger.cxx particleadvection/ParticleMessenger.cxx @@ -234,6 +233,18 @@ set(gradient_sources_device vtkm_pyexpander_generated_file(ClipWithFieldExternInstantiations.h) vtkm_pyexpander_generated_file(ClipWithImplicitFunctionExternInstantiations.h) +set(core_headers + NewFilter.h) +set(core_sources_device + NewFilter.cxx) + +vtkm_library( + NAME vtkm_filter_core + HEADERS ${core_headers} + DEVICE_SOURCES ${core_sources_device} + USE_VTKM_JOB_POOL +) + add_library(vtkm_filter INTERFACE) vtkm_library( @@ -270,6 +281,7 @@ vtkm_library( set_target_properties( vtkm_filter_common + vtkm_filter_core vtkm_filter_extra vtkm_filter_contour vtkm_filter_gradient @@ -279,6 +291,7 @@ set_target_properties( ) target_link_libraries(vtkm_filter_common PUBLIC vtkm_worklet) +target_link_libraries(vtkm_filter_core PUBLIC vtkm_cont) target_link_libraries(vtkm_filter_extra PUBLIC vtkm_filter_common) target_link_libraries(vtkm_filter_contour PUBLIC vtkm_filter_common) target_link_libraries(vtkm_filter_gradient PUBLIC vtkm_filter_common) @@ -293,6 +306,7 @@ target_link_libraries(vtkm_filter PUBLIC INTERFACE vtkm_filter_contour vtkm_filter_gradient vtkm_filter_common + vtkm_filter_core ) install(TARGETS vtkm_filter EXPORT ${VTKm_EXPORT_NAME}) @@ -300,6 +314,7 @@ install(TARGETS vtkm_filter EXPORT ${VTKm_EXPORT_NAME}) add_subdirectory(internal) add_subdirectory(particleadvection) +add_subdirectory(field_transform) #-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - if (VTKm_ENABLE_TESTING) diff --git a/vtkm/filter/GenerateIds.h b/vtkm/filter/GenerateIds.h index 0cc5e4013..7af613132 100644 --- a/vtkm/filter/GenerateIds.h +++ b/vtkm/filter/GenerateIds.h @@ -10,95 +10,31 @@ #ifndef vtk_m_filter_GenerateIds_h #define vtk_m_filter_GenerateIds_h -#include - -#include +#include +#include namespace vtkm { namespace filter { -/// \brief Adds fields to a `DataSet` that give the ids for the points and cells. -/// -/// This filter will add (by default) a point field named `pointids` that gives the -/// index of the associated point and likewise a cell field named `cellids` for the -/// associated cell indices. These fields are useful for tracking the provenance of -/// the elements of a `DataSet` as it gets manipulated by filters. It is also -/// convenient for adding indices to operations designed for fields and generally -/// creating test data. -/// -class VTKM_FILTER_EXTRA_EXPORT GenerateIds : public vtkm::filter::Filter +VTKM_DEPRECATED( + 1.8, + "Use vtkm/filter/field_transform/GenerateIds.h instead of vtkm/filter/GenerateIds.h.") +inline void GenerateIds_deprecated() {} + +inline void GenerateIds_deprecated_warning() { - std::string PointFieldName = "pointids"; - std::string CellFieldName = "cellids"; - bool GeneratePointIds = true; - bool GenerateCellIds = true; - bool UseFloat = false; + GenerateIds_deprecated(); +} -public: - GenerateIds() = default; - ~GenerateIds() = default; - - /// \{ - /// \brief The name given to the generated point field. - /// - /// By default, the name is `pointids`. - /// - const std::string& GetPointFieldName() const { return this->PointFieldName; } - void SetPointFieldName(const std::string& name) { this->PointFieldName = name; } - /// \} - - /// \{ - /// \brief The name given to the generated cell field. - /// - /// By default, the name is `cellids`. - /// - const std::string& GetCellFieldName() const { return this->CellFieldName; } - void SetCellFieldName(const std::string& name) { this->CellFieldName = name; } - /// \} - - /// \{ - /// \brief Specify whether the point id field is generated. - /// - /// When `GeneratePointIds` is `true` (the default), a field echoing the point - /// indices is generated. When set to `false`, this output is not created. - /// - bool GetGeneratePointIds() const { return this->GeneratePointIds; } - void SetGeneratePointIds(bool flag) { this->GeneratePointIds = flag; } - /// \} - - /// \{ - /// \brief Specify whether the cell id field is generated. - /// - /// When `GenerateCellIds` is `true` (the default), a field echoing the cell - /// indices is generated. When set to `false`, this output is not created. - /// - bool GetGenerateCellIds() const { return this->GenerateCellIds; } - void SetGenerateCellIds(bool flag) { this->GenerateCellIds = flag; } - /// \} - - /// \{ - /// \brief Specify whether the generated fields should be integer or float. - /// - /// When `UseFloat` is `false` (the default), then the fields generated will have - /// type `vtkm::Id`. If it is set to `true`, then the fields will be generated - /// with type `vtkm::FloatDefault`. - /// - bool GetUseFloat() const { return this->UseFloat; } - void SetUseFloat(bool flag) { this->UseFloat = flag; } - - vtkm::cont::DataSet DoExecute(const vtkm::cont::DataSet& input) const; - - template - vtkm::cont::DataSet PrepareForExecution(const vtkm::cont::DataSet& input, - vtkm::filter::PolicyBase) const - { - return this->DoExecute(input); - } +class VTKM_DEPRECATED(1.8, "Use vtkm::filter::field_transform::GenerateIds.") GenerateIds + : public vtkm::filter::field_transform::GenerateIds +{ + using field_transform::GenerateIds::GenerateIds; }; +} } // namespace vtkm::filter -} // namespace vtkm #endif //vtk_m_filter_GenerateIds_h diff --git a/vtkm/filter/NewFilter.cxx b/vtkm/filter/NewFilter.cxx new file mode 100644 index 000000000..c893e28aa --- /dev/null +++ b/vtkm/filter/NewFilter.cxx @@ -0,0 +1,136 @@ +//============================================================================ +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//============================================================================ +#include +#include +#include + +#include +#include + +#include + +namespace vtkm +{ +namespace filter +{ +namespace +{ +void RunFilter(NewFilter* self, + vtkm::filter::DataSetQueue& input, + vtkm::filter::DataSetQueue& output) +{ + std::pair task; + while (input.GetTask(task)) + { + auto outDS = self->Execute(task.second); + output.Push(std::make_pair(task.first, std::move(outDS))); + } + + vtkm::cont::Algorithm::Synchronize(); +} +} // anonymous namespace + +NewFilter::~NewFilter() = default; + +bool NewFilter::CanThread() const +{ + return true; +} + +//---------------------------------------------------------------------------- +vtkm::cont::PartitionedDataSet NewFilter::DoExecutePartitions( + const vtkm::cont::PartitionedDataSet& input) +{ + vtkm::cont::PartitionedDataSet output; + + if (this->GetRunMultiThreadedFilter()) + { + vtkm::filter::DataSetQueue inputQueue(input); + vtkm::filter::DataSetQueue outputQueue; + + vtkm::Id numThreads = this->DetermineNumberOfThreads(input); + + //Run 'numThreads' filters. + std::vector> futures(static_cast(numThreads)); + for (std::size_t i = 0; i < static_cast(numThreads); i++) + { + auto f = std::async( + std::launch::async, RunFilter, this, std::ref(inputQueue), std::ref(outputQueue)); + futures[i] = std::move(f); + } + + for (auto& f : futures) + f.get(); + + //Get results from the outputQueue. + output = outputQueue.Get(); + } + else + { + for (const auto& inBlock : input) + { + vtkm::cont::DataSet outBlock = this->Execute(inBlock); + output.AppendPartition(outBlock); + } + } + + return output; +} + +vtkm::cont::DataSet NewFilter::Execute(const vtkm::cont::DataSet& input) +{ + return this->DoExecute(input); +} + +vtkm::cont::PartitionedDataSet NewFilter::Execute(const vtkm::cont::PartitionedDataSet& input) +{ + VTKM_LOG_SCOPE(vtkm::cont::LogLevel::Perf, + "NewFilter (%d partitions): '%s'", + (int)input.GetNumberOfPartitions(), + vtkm::cont::TypeToString().c_str()); + + vtkm::cont::PartitionedDataSet output = this->DoExecutePartitions(input); + return output; +} + +vtkm::Id NewFilter::DetermineNumberOfThreads(const vtkm::cont::PartitionedDataSet& input) +{ + vtkm::Id numDS = input.GetNumberOfPartitions(); + + //Aribitrary constants. + const vtkm::Id threadsPerGPU = 8; + const vtkm::Id threadsPerCPU = 4; + + vtkm::Id availThreads = 1; + + auto& tracker = vtkm::cont::GetRuntimeDeviceTracker(); + + if (tracker.CanRunOn(vtkm::cont::DeviceAdapterTagCuda{})) + availThreads = threadsPerGPU; + else if (tracker.CanRunOn(vtkm::cont::DeviceAdapterTagKokkos{})) + { + //Kokkos doesn't support threading on the CPU. +#ifdef VTKM_KOKKOS_CUDA + availThreads = threadsPerGPU; +#else + availThreads = 1; +#endif + } + else if (tracker.CanRunOn(vtkm::cont::DeviceAdapterTagSerial{})) + availThreads = 1; + else + availThreads = threadsPerCPU; + + vtkm::Id numThreads = std::min(numDS, availThreads); + return numThreads; +} + +} // namespace filter +} // namespace vtkm diff --git a/vtkm/filter/NewFilter.h b/vtkm/filter/NewFilter.h new file mode 100644 index 000000000..06ba81730 --- /dev/null +++ b/vtkm/filter/NewFilter.h @@ -0,0 +1,362 @@ +//============================================================================ +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//============================================================================ +#ifndef vtk_m_filter_NewFilter_h +#define vtk_m_filter_NewFilter_h + +#include +#include +#include +#include +#include + +#include +#include + +namespace vtkm +{ +namespace filter +{ +/// \brief base class for all filters. +/// +/// This is the base class for all filters. To add a new filter, one can subclass this (or any of +/// the existing subclasses e.g. FilterField, FilterParticleAdvection, etc.) and implement relevant +/// methods. +/// +/// \section FilterUsage Usage +/// +/// To execute a filter, one typically calls the `auto result = filter.Execute(input)`. Typical +/// usage is as follows: +/// +/// \code{cpp} +/// +/// // create the concrete subclass (e.g. Contour). +/// vtkm::filter::contour::Contour contour; +/// +/// // select fields to map to the output, if different from default which is to map all input +/// // fields. +/// contour.SetFieldToPass({"var1", "var2"}); +/// +/// // execute the filter on vtkm::cont::DataSet. +/// vtkm::cont::DataSet dsInput = ... +/// auto outputDS = contour.Execute(dsInput); +/// +/// // or, execute on a vtkm::cont::PartitionedDataSet +/// vtkm::cont::PartitionedDataSet mbInput = ... +/// auto outputMB = contour.Execute(mbInput); +/// \endcode +/// +/// `Execute` methods take in the input DataSet or PartitionedDataSet to process and return the +/// result. The type of the result is same as the input type, thus `Execute(DataSet&)` returns +/// a DataSet while `Execute(PartitionedDataSet&)` returns a PartitionedDataSet. +/// +/// `Execute` simply calls the pure virtual function `DoExecute(DataSet&)` which is the main +/// extension point of the Filter interface. Filter developer needs to override +/// `DoExecute(DataSet)` to implement the business logic of filtering operations on a single +/// DataSet. +/// +/// The default implementation of `Execute(PartitionedDataSet&)` is merely provided for +/// convenience. Internally, it calls `DoExecutePartitions(PartitionedDataSet)` to iterate DataSets +/// of a PartitionedDataSet and pass each individual DataSets to `DoExecute(DataSet&)`, +/// possibly in a multi-threaded setting. Developer of `DoExecute(DataSet&)` needs to indicate +/// the thread-safeness of `DoExecute(DataSet&)` by overriding the `CanThread()` virtual method +/// which by default returns `true`. +/// +/// In the case that filtering on a PartitionedDataSet can not be simply implemented as a +/// for-each loop on the component DataSets, filter implementor needs to override the +/// `DoExecutePartitions(PartitionedDataSet&)`. See the implementation of +/// `FilterParticleAdvection::Execute(PartitionedDataSet&)` for an example. +/// +/// \section FilterSubclassing Subclassing +/// +/// In many uses cases, one subclasses one of the immediate subclasses of this class such as +/// FilterField, FilterParticleAdvection, etc. Those may impose additional constraints on the +/// methods to implement in the subclasses. Here, we describes the things to consider when directly +/// subclassing vtkm::filter::NewFilter. +/// +/// \subsection FilterExecution Execute +/// +/// A concrete subclass of Filter must provide `DoExecute` implementation that provides the meat +/// for the filter i.e. the implementation for the filter's data processing logic. There are two +/// signatures available; which one to implement depends on the nature of the filter. +/// +/// Let's consider simple filters that do not need to do anything special to handle +/// PartitionedDataSet e.g. clip, contour, etc. These are the filters where executing the filter +/// on a PartitionedDataSet simply means executing the filter on one partition at a time and +/// packing the output for each iteration info the result PartitionedDataSet. For such filters, +/// one must implement the following signature. +/// +/// \code{cpp} +/// +/// vtkm::cont::DataSet DoExecute(const vtkm::cont::DataSet& input); +/// +/// \endcode +/// +/// The role of this method is to execute on the input dataset and generate the result and return +/// it. If there are any errors, the subclass must throw an exception +/// (e.g. `vtkm::cont::ErrorFilterExecution`). +/// +/// In this simple case, the NewFilter superclass handles iterating over multiple partitions in the +/// input PartitionedDataSet and calling `DoExecute(DataSet&)` iteratively. +/// +/// The aforementioned approach is also suitable for filters that need special handling for +/// PartitionedDataSets that requires certain cross DataSet operations (usually scatter/gather +/// and reduction on DataSets) before and/or after the per DataSet operation. This can be done by +/// overriding `DoExecutePartitions(PartitionedDataSet&)` while calling to the base class +/// `DoExecutePartitions(PartitionedDataSet&) as helper function for iteration on DataSets. +/// +/// \code{cpp} +/// vtkm::cont::PartitionedDataSet FooFilter::DoExecutePartitions( +/// const vtkm::cont::PartitionedDataSet& input) +/// { +/// // Do pre execute stuff, e.g. scattering to each DataSet +/// auto output = this->NewFilter::DoExecutePartitions(input); +/// // Do post execute stuff, e.g gather/reduce from DataSets +/// return output; +/// } +/// \endcode +/// +/// For more complex filters, like streamlines, particle tracking, where the processing of +/// PartitionedDataSets cannot be modelled as mapping and reduction operation on DataSet, one +/// needs fully implement `DoExecutePartitions(PartitionedDataSet&)`. Now the subclass is given +/// full control over the execution, including any mapping of fields to output (described in next +/// sub-section). +/// +/// \subsection FilterMappingFields MapFieldsOntoOutput +/// +/// For subclasses that map input fields into output fields, the implementation of its +/// `DoExecute(DataSet&)` should call `NewFilter::MapFieldsOntoOutput` with a properly defined +/// `Mapper`, before returning the output DataSet. For example: +/// +/// \code{cpp} +/// VTKM_CONT DataSet SomeFilter::DoExecute(const vtkm::cont::DataSet& input) +/// { +/// vtkm::cont::DataSet output; +/// output = ... // Generation of the new DataSet +/// +/// // Mapper is a callable object (function object, lambda, etc.) that takes an input Field +/// // and maps it to an output Field and then add the output Field to the output DataSet +/// auto mapper = [](auto& outputDs, const auto& inputField) { +/// auto outputField = ... // Business logic for mapping input field to output field +/// output.AddField(outputField); +/// }; +/// MapFieldsOntoOutput(input, output, mapper); +/// +/// return output; +/// } +/// \endcode +/// +/// `MapFieldsOntoOutput` iterates through each `FieldToPass` in the input DataSet and calls the +/// Mapper to map the input Field to output Field. For simple filters that just pass on input +/// fields to the output DataSet without any computation, an overload of +/// `MapFieldsOntoOutput(const vtkm::cont::DataSet& input, vtkm::cont::DataSet& output)` is also +/// provided as a convenience that uses the default mapper which trivially adds input Field to +/// output DaaSet (via a shallow copy). +/// +/// \subsection FilterThreadSafety CanThread +/// +/// By default, the implementation of `DoExecute(DataSet&)` should model a *pure function*, i.e. it +/// does not have any mutable shared state. This makes it thread-safe by default and allows +/// the default implementation of `DoExecutePartitions(PartitionedDataSet&)` to be simply a parallel +/// for-each, thus facilitates multi-threaded execution without any lock. +/// +/// Many legacy (VTKm 1.x) filter implementations needed to store states between the mesh generation +/// phase and field mapping phase of filter execution, for example, parameters for field +/// interpolation. The shared mutable states were mostly stored as mutable data members of the +/// filter class (either in terms of ArrayHandle or some kind of Worket). The new filter interface, +/// by combining the two phases into a single call to `DoExecute(DataSet&)`, we have eliminated most +/// of the cases that require such shared mutable states. New implementations of filters that +/// require passing information between these two phases can now use local variables within the +/// `DoExecute(DataSet&)`. For example: +/// +/// \code{cpp} +/// struct SharedState; // shared states between mesh generation and field mapping. +/// VTKM_CONT DataSet ThreadSafeFilter::DoExecute(const vtkm::cont::DataSet& input) +/// { +/// // Mutable states that was a data member of the filter is now a local variable. +/// // Each invocation of Execute(DataSet) in the multi-threaded execution of +/// // Execute(PartitionedDataSet&) will have a copy of `states` on each thread's stack +/// // thus making it thread-safe. +/// SharedStates states; +/// +/// vtkm::cont::DataSet output; +/// output = ... // Generation of the new DataSet and store interpolation parameters in `states` +/// +/// // Lambda capture of `states`, effectively passing the shared states to the Mapper. +/// auto mapper = [&states](auto& outputDs, const auto& inputField) { +/// auto outputField = ... // Use `states` for mapping input field to output field +/// output.AddField(outputField); +/// }; +/// MapFieldsOntoOutput(input, output, mapper); +/// +/// return output; +/// } +/// \endcode +/// +/// In the rare cases that filter implementation can not be made thread-safe, the implementation +/// needs to override the `CanThread()` virtual method to return `false`. The default +/// `Execute(PartitionedDataSet&)` implementation will fallback to a serial for loop execution. +/// +/// \subsection FilterThreadScheduling DoExecute +/// The default multi-threaded execution of `Execute(PartitionedDataSet&)` uses a simple FIFO queue +/// of DataSet and pool of *worker* threads. Implementation of Filter subclass can override the +/// `DoExecutePartitions(PartitionedDataSet)` virtual method to provide implementation specific +/// scheduling policy. The default number of *worker* threads in the pool are determined by the +/// `DetermineNumberOfThreads()` virtual method using several backend dependent heuristic. +/// Implementations of Filter subclass can also override +/// `DetermineNumberOfThreads()` to provide implementation specific heuristic. +/// +class VTKM_FILTER_CORE_EXPORT NewFilter +{ +public: + VTKM_CONT + virtual ~NewFilter(); + + VTKM_CONT + virtual bool CanThread() const; + + VTKM_CONT + bool GetRunMultiThreadedFilter() const + { + return this->CanThread() && this->RunFilterWithMultipleThreads; + } + + VTKM_CONT + void SetRunMultiThreadedFilter(bool val) + { + if (this->CanThread()) + this->RunFilterWithMultipleThreads = val; + else + { + std::string msg = + "Multi threaded filter not supported for " + std::string(typeid(*this).name()); + VTKM_LOG_S(vtkm::cont::LogLevel::Info, msg); + } + } + + /// \brief Specify which subset of types a filter supports. + /// + /// A filter is able to state what subset of types it supports. + using SupportedTypes = VTKM_DEFAULT_TYPE_LIST; + + //@{ + /// \brief Specify which fields get passed from input to output. + /// + /// After a filter successfully executes and returns a new data set, fields are mapped from + /// input to output. Depending on what operation the filter does, this could be a simple shallow + /// copy of an array, or it could be a computed operation. You can control which fields are + /// passed (and equivalently which are not) with this parameter. + /// + /// By default, all fields are passed during execution. + /// + VTKM_CONT + void SetFieldsToPass(const vtkm::filter::FieldSelection& fieldsToPass) + { + this->FieldsToPass = fieldsToPass; + } + + VTKM_CONT + void SetFieldsToPass(const vtkm::filter::FieldSelection& fieldsToPass, + vtkm::filter::FieldSelection::ModeEnum mode) + { + this->FieldsToPass = fieldsToPass; + this->FieldsToPass.SetMode(mode); + } + + VTKM_CONT + void SetFieldsToPass( + const std::string& fieldname, + vtkm::cont::Field::Association association, + vtkm::filter::FieldSelection::ModeEnum mode = vtkm::filter::FieldSelection::MODE_SELECT) + { + this->SetFieldsToPass({ fieldname, association }, mode); + } + + VTKM_CONT + const vtkm::filter::FieldSelection& GetFieldsToPass() const { return this->FieldsToPass; } + VTKM_CONT + vtkm::filter::FieldSelection& GetFieldsToPass() { return this->FieldsToPass; } + //@} + + //@{ + /// Select the coordinate system index to make active to use when processing the input + /// DataSet. This is used primarily by the Filter to select the coordinate system + /// to use as a field when \c UseCoordinateSystemAsField is true. + VTKM_CONT + void SetActiveCoordinateSystem(vtkm::Id index) { this->CoordinateSystemIndex = index; } + + VTKM_CONT + vtkm::Id GetActiveCoordinateSystemIndex() const { return this->CoordinateSystemIndex; } + //@} + + //@{ + /// Executes the filter on the input and produces a result dataset. + /// + /// On success, this the dataset produced. On error, vtkm::cont::ErrorExecution will be thrown. + VTKM_CONT vtkm::cont::DataSet Execute(const vtkm::cont::DataSet& input); + //@} + + //@{ + /// Executes the filter on the input PartitionedDataSet and produces a result PartitionedDataSet. + /// + /// On success, this the dataset produced. On error, vtkm::cont::ErrorExecution will be thrown. + VTKM_CONT vtkm::cont::PartitionedDataSet Execute(const vtkm::cont::PartitionedDataSet& input); + //@} + + // FIXME: Is this actually materialize? Are there different kinds of Invoker? + /// Specify the vtkm::cont::Invoker to be used to execute worklets by + /// this filter instance. Overriding the default allows callers to control + /// which device adapters a filter uses. + void SetInvoker(vtkm::cont::Invoker inv) { this->Invoke = inv; } + +protected: + vtkm::cont::Invoker Invoke; + vtkm::Id CoordinateSystemIndex = 0; + + template + VTKM_CONT void MapFieldsOntoOutput(const vtkm::cont::DataSet& input, + vtkm::cont::DataSet& output, + Mapper&& mapper) + { + for (vtkm::IdComponent cc = 0; cc < input.GetNumberOfFields(); ++cc) + { + auto field = input.GetField(cc); + if (this->GetFieldsToPass().IsFieldSelected(field)) + { + mapper(output, field); + } + } + } + + VTKM_CONT void MapFieldsOntoOutput(const vtkm::cont::DataSet& input, vtkm::cont::DataSet& output) + { + MapFieldsOntoOutput(input, output, defaultMapper); + } + +private: + VTKM_CONT + virtual vtkm::Id DetermineNumberOfThreads(const vtkm::cont::PartitionedDataSet& input); + + // Note: In C++, subclasses can override private methods of superclass. + VTKM_CONT virtual vtkm::cont::DataSet DoExecute(const vtkm::cont::DataSet& inData) = 0; + VTKM_CONT virtual vtkm::cont::PartitionedDataSet DoExecutePartitions( + const vtkm::cont::PartitionedDataSet& inData); + + static void defaultMapper(vtkm::cont::DataSet& output, const vtkm::cont::Field& field) + { + output.AddField(field); + }; + + vtkm::filter::FieldSelection FieldsToPass = vtkm::filter::FieldSelection::MODE_ALL; + bool RunFilterWithMultipleThreads = false; +}; +} +} // namespace vtkm::filter + +#endif diff --git a/vtkm/filter/field_transform/CMakeLists.txt b/vtkm/filter/field_transform/CMakeLists.txt new file mode 100644 index 000000000..e488a2421 --- /dev/null +++ b/vtkm/filter/field_transform/CMakeLists.txt @@ -0,0 +1,28 @@ +##============================================================================ +## Copyright (c) Kitware, Inc. +## All rights reserved. +## See LICENSE.txt for details. +## +## This software is distributed WITHOUT ANY WARRANTY; without even +## the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +## PURPOSE. See the above copyright notice for more information. +##============================================================================ +set(field_transform_headers + GenerateIds.h) +set(field_transform_sources_device + GenerateIds.cxx) + +vtkm_library( + NAME vtkm_filter_field_transform + HEADERS ${field_transform_headers} + DEVICE_SOURCES ${field_transform_sources_device} + USE_VTKM_JOB_POOL +) + +target_link_libraries(vtkm_filter_field_transform PUBLIC vtkm_worklet vtkm_filter_core) +target_link_libraries(vtkm_filter PUBLIC INTERFACE vtkm_filter_field_transform) + +#-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - +if (VTKm_ENABLE_TESTING) + add_subdirectory(testing) +endif () diff --git a/vtkm/filter/GenerateIds.cxx b/vtkm/filter/field_transform/GenerateIds.cxx similarity index 85% rename from vtkm/filter/GenerateIds.cxx rename to vtkm/filter/field_transform/GenerateIds.cxx index b058d826a..f1296f14d 100644 --- a/vtkm/filter/GenerateIds.cxx +++ b/vtkm/filter/field_transform/GenerateIds.cxx @@ -7,17 +7,15 @@ // the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR // PURPOSE. See the above copyright notice for more information. //============================================================================ - -#include - #include -#include #include +#include namespace { -vtkm::cont::UnknownArrayHandle GenerateArray(const vtkm::filter::GenerateIds& self, vtkm::Id size) +vtkm::cont::UnknownArrayHandle GenerateArray(const vtkm::filter::field_transform::GenerateIds& self, + vtkm::Id size) { vtkm::cont::ArrayHandleIndex indexArray(size); @@ -41,8 +39,9 @@ namespace vtkm { namespace filter { - -vtkm::cont::DataSet GenerateIds::DoExecute(const vtkm::cont::DataSet& input) const +namespace field_transform +{ +vtkm::cont::DataSet GenerateIds::DoExecute(const vtkm::cont::DataSet& input) { vtkm::cont::DataSet output = input; @@ -57,8 +56,10 @@ vtkm::cont::DataSet GenerateIds::DoExecute(const vtkm::cont::DataSet& input) con output.AddCellField(this->GetCellFieldName(), GenerateArray(*this, input.GetNumberOfCells())); } + MapFieldsOntoOutput(input, output); + return output; } - +} // namespace field_transform } // namespace vtkm::filter } // namespace vtkm diff --git a/vtkm/filter/field_transform/GenerateIds.h b/vtkm/filter/field_transform/GenerateIds.h new file mode 100644 index 000000000..1183c46a0 --- /dev/null +++ b/vtkm/filter/field_transform/GenerateIds.h @@ -0,0 +1,95 @@ +//============================================================================ +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//============================================================================ +#ifndef vtk_m_filter_field_transform_GenerateIds_h +#define vtk_m_filter_field_transform_GenerateIds_h + +#include +#include + +namespace vtkm +{ +namespace filter +{ +namespace field_transform +{ +/// \brief Adds fields to a `DataSet` that give the ids for the points and cells. +/// +/// This filter will add (by default) a point field named `pointids` that gives the +/// index of the associated point and likewise a cell field named `cellids` for the +/// associated cell indices. These fields are useful for tracking the provenance of +/// the elements of a `DataSet` as it gets manipulated by filters. It is also +/// convenient for adding indices to operations designed for fields and generally +/// creating test data. +/// +class VTKM_FILTER_FIELD_TRANSFORM_EXPORT GenerateIds : public vtkm::filter::NewFilter +{ + std::string PointFieldName = "pointids"; + std::string CellFieldName = "cellids"; + bool GeneratePointIds = true; + bool GenerateCellIds = true; + bool UseFloat = false; + +public: + /// \{ + /// \brief The name given to the generated point field. + /// + /// By default, the name is `pointids`. + /// + const std::string& GetPointFieldName() const { return this->PointFieldName; } + void SetPointFieldName(const std::string& name) { this->PointFieldName = name; } + /// \} + + /// \{ + /// \brief The name given to the generated cell field. + /// + /// By default, the name is `cellids`. + /// + const std::string& GetCellFieldName() const { return this->CellFieldName; } + void SetCellFieldName(const std::string& name) { this->CellFieldName = name; } + /// \} + + /// \{ + /// \brief Specify whether the point id field is generated. + /// + /// When `GeneratePointIds` is `true` (the default), a field echoing the point + /// indices is generated. When set to `false`, this output is not created. + /// + bool GetGeneratePointIds() const { return this->GeneratePointIds; } + void SetGeneratePointIds(bool flag) { this->GeneratePointIds = flag; } + /// \} + + /// \{ + /// \brief Specify whether the cell id field is generated. + /// + /// When `GenerateCellIds` is `true` (the default), a field echoing the cell + /// indices is generated. When set to `false`, this output is not created. + /// + bool GetGenerateCellIds() const { return this->GenerateCellIds; } + void SetGenerateCellIds(bool flag) { this->GenerateCellIds = flag; } + /// \} + + /// \{ + /// \brief Specify whether the generated fields should be integer or float. + /// + /// When `UseFloat` is `false` (the default), then the fields generated will have + /// type `vtkm::Id`. If it is set to `true`, then the fields will be generated + /// with type `vtkm::FloatDefault`. + /// + bool GetUseFloat() const { return this->UseFloat; } + void SetUseFloat(bool flag) { this->UseFloat = flag; } + +private: + vtkm::cont::DataSet DoExecute(const vtkm::cont::DataSet& input) override; +}; +} // namespace field_transform +} // namespace vtkm::filter +} // namespace vtkm + +#endif //vtk_m_filter_field_transform_GenerateIds_h diff --git a/vtkm/filter/field_transform/testing/CMakeLists.txt b/vtkm/filter/field_transform/testing/CMakeLists.txt new file mode 100644 index 000000000..8b1d72d89 --- /dev/null +++ b/vtkm/filter/field_transform/testing/CMakeLists.txt @@ -0,0 +1,22 @@ +##============================================================================ +## Copyright (c) Kitware, Inc. +## All rights reserved. +## See LICENSE.txt for details. +## +## This software is distributed WITHOUT ANY WARRANTY; without even +## the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +## PURPOSE. See the above copyright notice for more information. +##============================================================================ + +set(unit_tests + UnitTestGenerateIds.cxx) + +set(libraries + vtkm_filter_field_transform + vtkm_source) + +vtkm_unit_tests( + SOURCES ${unit_tests} + LIBRARIES ${libraries} + USE_VTKM_JOB_POOL +) diff --git a/vtkm/filter/testing/UnitTestGenerateIds.cxx b/vtkm/filter/field_transform/testing/UnitTestGenerateIds.cxx similarity index 92% rename from vtkm/filter/testing/UnitTestGenerateIds.cxx rename to vtkm/filter/field_transform/testing/UnitTestGenerateIds.cxx index b3b410746..312dad2df 100644 --- a/vtkm/filter/testing/UnitTestGenerateIds.cxx +++ b/vtkm/filter/field_transform/testing/UnitTestGenerateIds.cxx @@ -7,14 +7,10 @@ // the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR // PURPOSE. See the above copyright notice for more information. //============================================================================ - -#include - -#include - #include - #include +#include +#include namespace { @@ -38,8 +34,9 @@ void CheckField(const vtkm::cont::UnknownArrayHandle& array, vtkm::Id expectedSi } } -void TryGenerateIds(vtkm::filter::GenerateIds& filter, // Why is Filter::Execute not const? - const vtkm::cont::DataSet& input) +void TryGenerateIds( + vtkm::filter::field_transform::GenerateIds& filter, // Why is Filter::Execute not const? + const vtkm::cont::DataSet& input) { vtkm::cont::DataSet output = filter.Execute(input); VTKM_TEST_ASSERT(output.GetNumberOfPoints() == input.GetNumberOfPoints()); @@ -81,7 +78,7 @@ void TryGenerateIds(vtkm::filter::GenerateIds& filter, // Why is Filter::Execute void TestGenerateIds() { vtkm::cont::DataSet input = vtkm::source::Tangle{ vtkm::Id3(8) }.Execute(); - vtkm::filter::GenerateIds filter; + vtkm::filter::field_transform::GenerateIds filter; TryGenerateIds(filter, input); diff --git a/vtkm/filter/testing/CMakeLists.txt b/vtkm/filter/testing/CMakeLists.txt index 415b15570..3195fd64d 100644 --- a/vtkm/filter/testing/CMakeLists.txt +++ b/vtkm/filter/testing/CMakeLists.txt @@ -37,7 +37,6 @@ set(unit_tests UnitTestFieldToColors.cxx UnitTestGradientExplicit.cxx UnitTestGradientUniform.cxx - UnitTestGenerateIds.cxx UnitTestGhostCellClassify.cxx UnitTestGhostCellRemove.cxx UnitTestHistogramFilter.cxx diff --git a/vtkm/filter/testing/UnitTestContourFilter.cxx b/vtkm/filter/testing/UnitTestContourFilter.cxx index 335264ea2..f39ef5eee 100644 --- a/vtkm/filter/testing/UnitTestContourFilter.cxx +++ b/vtkm/filter/testing/UnitTestContourFilter.cxx @@ -15,7 +15,7 @@ #include #include -#include +#include #include #include @@ -32,7 +32,7 @@ public: vtkm::Id3 dims(4, 4, 4); vtkm::source::Tangle tangle(dims); - vtkm::filter::GenerateIds genIds; + vtkm::filter::field_transform::GenerateIds genIds; genIds.SetUseFloat(true); genIds.SetGeneratePointIds(false); genIds.SetCellFieldName("cellvar"); diff --git a/vtkm/worklet/testing/UnitTestContour.cxx b/vtkm/worklet/testing/UnitTestContour.cxx index e97405cdc..5d2906fe6 100644 --- a/vtkm/worklet/testing/UnitTestContour.cxx +++ b/vtkm/worklet/testing/UnitTestContour.cxx @@ -16,7 +16,7 @@ #include #include -#include +#include #include #include @@ -186,7 +186,7 @@ void TestContourUniformGrid() vtkm::Id3 dims(4, 4, 4); vtkm::source::Tangle tangle(dims); - vtkm::filter::GenerateIds genIds; + vtkm::filter::field_transform::GenerateIds genIds; genIds.SetUseFloat(true); genIds.SetGeneratePointIds(false); genIds.SetCellFieldName("cellvar"); @@ -365,7 +365,7 @@ void TestContourClipped() vtkm::Id3 dims(4, 4, 4); vtkm::source::Tangle tangle(dims); - vtkm::filter::GenerateIds genIds; + vtkm::filter::field_transform::GenerateIds genIds; genIds.SetUseFloat(true); genIds.SetGeneratePointIds(false); genIds.SetCellFieldName("cellvar");