From 8cf02d3632471280d7b98b581bb51598081607c5 Mon Sep 17 00:00:00 2001 From: Li-Ta Lo Date: Fri, 3 Dec 2021 13:44:51 -0700 Subject: [PATCH 1/7] New Filter Interface Design --- vtkm/filter/CMakeLists.txt | 18 +- vtkm/filter/FieldTransform/CMakeLists.txt | 33 ++ .../{ => FieldTransform}/GenerateIds.cxx | 9 +- .../filter/{ => FieldTransform}/GenerateIds.h | 19 +- .../FieldTransform/testing/CMakeLists.txt | 22 ++ .../testing/UnitTestGenerateIds.cxx | 8 +- vtkm/filter/NewFilter.cxx | 132 +++++++ vtkm/filter/NewFilter.h | 365 ++++++++++++++++++ vtkm/filter/testing/CMakeLists.txt | 1 - vtkm/filter/testing/UnitTestContourFilter.cxx | 2 +- vtkm/worklet/testing/UnitTestContour.cxx | 2 +- 11 files changed, 580 insertions(+), 31 deletions(-) create mode 100644 vtkm/filter/FieldTransform/CMakeLists.txt rename vtkm/filter/{ => FieldTransform}/GenerateIds.cxx (89%) rename vtkm/filter/{ => FieldTransform}/GenerateIds.h (85%) create mode 100644 vtkm/filter/FieldTransform/testing/CMakeLists.txt rename vtkm/filter/{ => FieldTransform}/testing/UnitTestGenerateIds.cxx (98%) create mode 100644 vtkm/filter/NewFilter.cxx create mode 100644 vtkm/filter/NewFilter.h diff --git a/vtkm/filter/CMakeLists.txt b/vtkm/filter/CMakeLists.txt index fc9829d56..3c8154ec6 100644 --- a/vtkm/filter/CMakeLists.txt +++ b/vtkm/filter/CMakeLists.txt @@ -89,7 +89,6 @@ set(extra_headers ExternalFaces.h FieldSelection.h FieldToColors.h - GenerateIds.h GhostCellClassify.h GhostCellRemove.h Histogram.h @@ -191,7 +190,6 @@ set(extra_sources_device ${ClipWithFieldInstantiations} ${ClipWithImplicitFunctionInstantiations} ExternalFaces.cxx - GenerateIds.cxx VectorMagnitude.cxx particleadvection/Messenger.cxx particleadvection/ParticleMessenger.cxx @@ -234,6 +232,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 +280,7 @@ vtkm_library( set_target_properties( vtkm_filter_common + vtkm_filter_core vtkm_filter_extra vtkm_filter_contour vtkm_filter_gradient @@ -279,6 +290,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 +305,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 +313,7 @@ install(TARGETS vtkm_filter EXPORT ${VTKm_EXPORT_NAME}) add_subdirectory(internal) add_subdirectory(particleadvection) +add_subdirectory(FieldTransform) #-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - if (VTKm_ENABLE_TESTING) diff --git a/vtkm/filter/FieldTransform/CMakeLists.txt b/vtkm/filter/FieldTransform/CMakeLists.txt new file mode 100644 index 000000000..c6ddf467c --- /dev/null +++ b/vtkm/filter/FieldTransform/CMakeLists.txt @@ -0,0 +1,33 @@ +##============================================================================ +## 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(fieldtransform_headers + GenerateIds.h) +set(fieldtransform_sources_device + GenerateIds.cxx) + +vtkm_library( + NAME vtkm_filter_fieldtransform + HEADERS ${fieldtransform_headers} + DEVICE_SOURCES ${fieldtransform_sources_device} + USE_VTKM_JOB_POOL +) + +set_property(TARGET + vtkm_filter_fieldtransform + PROPERTY UNITY_BUILD_MODE GROUP + ) + +target_link_libraries(vtkm_filter_fieldtransform PUBLIC vtkm_worklet vtkm_filter_core) +target_link_libraries(vtkm_filter PUBLIC INTERFACE vtkm_filter_fieldtransform) + +#-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - +if (VTKm_ENABLE_TESTING) + add_subdirectory(testing) +endif () diff --git a/vtkm/filter/GenerateIds.cxx b/vtkm/filter/FieldTransform/GenerateIds.cxx similarity index 89% rename from vtkm/filter/GenerateIds.cxx rename to vtkm/filter/FieldTransform/GenerateIds.cxx index b058d826a..b9fe9b0e3 100644 --- a/vtkm/filter/GenerateIds.cxx +++ b/vtkm/filter/FieldTransform/GenerateIds.cxx @@ -7,12 +7,9 @@ // 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 { @@ -42,7 +39,7 @@ namespace vtkm namespace filter { -vtkm::cont::DataSet GenerateIds::DoExecute(const vtkm::cont::DataSet& input) const +vtkm::cont::DataSet GenerateIds::Execute(const vtkm::cont::DataSet& input) { vtkm::cont::DataSet output = input; @@ -57,6 +54,8 @@ vtkm::cont::DataSet GenerateIds::DoExecute(const vtkm::cont::DataSet& input) con output.AddCellField(this->GetCellFieldName(), GenerateArray(*this, input.GetNumberOfCells())); } + MapFieldsOntoOutput(input, output); + return output; } diff --git a/vtkm/filter/GenerateIds.h b/vtkm/filter/FieldTransform/GenerateIds.h similarity index 85% rename from vtkm/filter/GenerateIds.h rename to vtkm/filter/FieldTransform/GenerateIds.h index 0cc5e4013..8b03b49c1 100644 --- a/vtkm/filter/GenerateIds.h +++ b/vtkm/filter/FieldTransform/GenerateIds.h @@ -10,9 +10,8 @@ #ifndef vtk_m_filter_GenerateIds_h #define vtk_m_filter_GenerateIds_h -#include - -#include +#include +#include namespace vtkm { @@ -28,7 +27,7 @@ namespace filter /// convenient for adding indices to operations designed for fields and generally /// creating test data. /// -class VTKM_FILTER_EXTRA_EXPORT GenerateIds : public vtkm::filter::Filter +class VTKM_FILTER_FIELDTRANSFORM_EXPORT GenerateIds : public vtkm::filter::NewFilter { std::string PointFieldName = "pointids"; std::string CellFieldName = "cellids"; @@ -37,9 +36,6 @@ class VTKM_FILTER_EXTRA_EXPORT GenerateIds : public vtkm::filter::FilterUseFloat; } 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); - } + vtkm::cont::DataSet Execute(const vtkm::cont::DataSet& input) override; }; } // namespace vtkm::filter diff --git a/vtkm/filter/FieldTransform/testing/CMakeLists.txt b/vtkm/filter/FieldTransform/testing/CMakeLists.txt new file mode 100644 index 000000000..fd25171c6 --- /dev/null +++ b/vtkm/filter/FieldTransform/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_fieldtransform + 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/FieldTransform/testing/UnitTestGenerateIds.cxx similarity index 98% rename from vtkm/filter/testing/UnitTestGenerateIds.cxx rename to vtkm/filter/FieldTransform/testing/UnitTestGenerateIds.cxx index b3b410746..91f05fe07 100644 --- a/vtkm/filter/testing/UnitTestGenerateIds.cxx +++ b/vtkm/filter/FieldTransform/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 { diff --git a/vtkm/filter/NewFilter.cxx b/vtkm/filter/NewFilter.cxx new file mode 100644 index 000000000..c4d2c428c --- /dev/null +++ b/vtkm/filter/NewFilter.cxx @@ -0,0 +1,132 @@ +//============================================================================ +// 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 + +#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 + +//---------------------------------------------------------------------------- +vtkm::cont::PartitionedDataSet NewFilter::DoExecute(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::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()); + + // Call `void Derived::PreExecute(input, policy)`, if defined. + this->PreExecute(input); + + // Call `PrepareForExecution` (which should probably be renamed at some point) + vtkm::cont::PartitionedDataSet output = this->DoExecute(input); + + // Call `Derived::PostExecute(input, output, policy)` if defined. + this->PostExecute(input, output); + + 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..fe52fd9bc --- /dev/null +++ b/vtkm/filter/NewFilter.h @@ -0,0 +1,365 @@ +//============================================================================ +// 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 + +#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, +/// FilterDataSet, FilterDataSetWithField, 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. MarchingCubes). +/// vtkm::filter::MarchingCubes marchingCubes; +/// +/// // select fieds to map to the output, if different from default which is to +/// // map all input fields. +/// marchingCubes.SetFieldToPass({"var1", "var2"}); +/// +/// // execute the filter on vtkm::cont::DataSet. +/// vtkm::cont::DataSet dsInput = ... +/// auto outputDS = filter.Execute(dsInput); +/// +/// // or, execute on a vtkm::cont::PartitionedDataSet +/// vtkm::cont::PartitionedDataSet mbInput = ... +/// auto outputMB = filter.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. +/// +/// The implementation for `Execute(DataSet&)` is merely provided for +/// convenience. Internally, it creates a PartitionedDataSet with a single +/// partition for the input and then forwards the call to +/// `Execute(PartitionedDataSet&)`. The method returns the first partition, if +/// any, from the PartitionedDataSet returned by the forwarded call. If the +/// PartitionedDataSet returned has more than 1 partition, then +/// `vtkm::cont::ErrorFilterExecution` will be thrown. +/// +/// \section FilterSubclassing Subclassing +/// +/// Typically, one subclasses one of the immediate subclasses of this class such as +/// FilterField, FilterDataSet, FilterDataSetWithField, 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::Filter. +/// +/// \subsection FilterPreExecutePostExecute PreExecute and PostExecute +/// +/// Subclasses may provide implementations for either or both of the following +/// methods. +/// +/// \code{cpp} +/// +/// template +/// void PreExecute(const vtkm::cont::PartitionedDataSet& input, +/// const vtkm::filter::PolicyBase& policy); +/// +/// template +/// void PostExecute(const vtkm::cont::PartitionedDataSet& input, vtkm::cont::PartitionedDataSet& output +/// const vtkm::filter::PolicyBase& policy); +/// +/// \endcode +/// +/// As the name suggests, these are called and the beginning and before the end +/// of an `Filter::Execute` call. Most filters that don't need to handle +/// PartitionedDataSet specially, e.g. clip, cut, iso-contour, need not worry +/// about these methods or provide any implementation. If, however, your filter +/// needs do to some initialization e.g. allocation buffers to accumulate +/// results, or finalization e.g. reduce results across all partitions, then +/// these methods provide convenient hooks for the same. +/// +/// \subsection FilterPrepareForExecution PrepareForExecution +/// +/// A concrete subclass of Filter must provide `PrepareForExecution` +/// 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} +/// +/// template +/// vtkm::cont::DataSet PrepareForExecution( +/// const vtkm::cont::DataSet& input, +/// const vtkm::filter::PolicyBase& policy); +/// +/// \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 case, the Filter superclass handles iterating over multiple +/// partitions in the input PartitionedDataSet and calling +/// `PrepareForExecution` iteratively. +/// +/// The aforementioned approach is also suitable for filters that need special +/// handling for PartitionedDataSets which can be modelled as PreExecute and +/// PostExecute steps (e.g. `vtkm::filter::Histogram`). +/// +/// For more complex filters, like streamlines, particle tracking, where the +/// processing of PartitionedDataSets cannot be modelled as a reduction of the +/// results, one can implement the following signature. +/// +/// \code{cpp} +/// template +/// vtkm::cont::PartitionedDataSet PrepareForExecution( +/// const vtkm::cont::PartitionedDataSet& input, +/// const vtkm::filter::PolicyBase& policy); +/// \endcode +/// +/// The responsibility of this method is the same, except now the subclass is +/// given full control over the execution, including any mapping of fields to +/// output (described in next sub-section). +/// +/// \subsection FilterMapFieldOntoOutput DoMapField +/// +/// Subclasses may provide `DoMapField` method with the following +/// signature: +/// +/// \code{cpp} +/// +/// template +/// VTKM_CONT bool DoMapField(vtkm::cont::DataSet& result, +/// const vtkm::cont::Field& field, +/// const vtkm::filter::PolicyBase& policy); +/// +/// \endcode +/// +/// When present, this method will be called after each partition execution to +/// map an input field from the corresponding input partition to the output +/// partition. +/// +class VTKM_FILTER_CORE_EXPORT NewFilter +{ +public: + VTKM_CONT + virtual ~NewFilter() = default; + + VTKM_CONT + virtual bool CanThread() const { return true; } + + 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 additional field storage to support. + /// + /// When a filter gets a field value from a DataSet, it has to determine what type + /// of storage the array has. Typically this is taken from the default storage + /// types defined in DefaultTypes.h. In some cases it is useful to support additional + /// types. For example, the filter might make sense to support ArrayHandleIndex or + /// ArrayHandleConstant. If so, the storage of those additional types should be + /// listed here. + using AdditionalFieldStorage = vtkm::ListEmpty; + + /// \brief Specify which structured cell sets to support. + /// + /// When a filter gets a cell set from a DataSet, it has to determine what type + /// of concrete cell set it is. This provides a list of supported structured + /// cell sets. + using SupportedStructuredCellSets = VTKM_DEFAULT_CELL_SET_LIST_STRUCTURED; + + /// \brief Specify which unstructured cell sets to support. + /// + /// When a filter gets a cell set from a DataSet, it has to determine what type + /// of concrete cell set it is. This provides a list of supported unstructured + /// cell sets. + using SupportedUnstructuredCellSets = VTKM_DEFAULT_CELL_SET_LIST_UNSTRUCTURED; + + /// \brief Specify which unstructured cell sets to support. + /// + /// When a filter gets a cell set from a DataSet, it has to determine what type + /// of concrete cell set it is. This provides a list of supported cell sets. + using SupportedCellSets = VTKM_DEFAULT_CELL_SET_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 virtual vtkm::cont::DataSet Execute(const vtkm::cont::DataSet& input) = 0; + //@} + + //@{ + /// 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 virtual 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; } + + // TODO: de-virtual, move to protected. + VTKM_CONT + virtual vtkm::Id DetermineNumberOfThreads(const vtkm::cont::PartitionedDataSet& input); + +protected: + vtkm::cont::Invoker Invoke; + vtkm::Id CoordinateSystemIndex = 0; + + //@{ + /// when operating on vtkm::cont::PartitionedDataSet, we + /// want to do processing across ranks as well. Just adding pre/post handles + /// for the same does the trick. + VTKM_CONT virtual void PreExecute(const vtkm::cont::PartitionedDataSet&) {} + + VTKM_CONT virtual void PostExecute(const vtkm::cont::PartitionedDataSet&, + vtkm::cont::PartitionedDataSet&) + { + } + //@} + + VTKM_CONT virtual vtkm::cont::PartitionedDataSet DoExecute( + const vtkm::cont::PartitionedDataSet& inData); + + static void defaultMapper(vtkm::cont::DataSet& output, const vtkm::cont::Field& field) + { + output.AddField(field); + }; + + template + VTKM_CONT void MapFieldsOntoOutput(const vtkm::cont::DataSet& input, + vtkm::cont::DataSet& output, + Mapper&& mapper) + { + // TODO: in the future of C++20, we can make it a "filtered_view". + 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::filter::FieldSelection FieldsToPass = vtkm::filter::FieldSelection::MODE_ALL; + bool RunFilterWithMultipleThreads = false; +}; +} +} // namespace vtkm::filter + +#endif 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..7356dfbe3 100644 --- a/vtkm/filter/testing/UnitTestContourFilter.cxx +++ b/vtkm/filter/testing/UnitTestContourFilter.cxx @@ -15,7 +15,7 @@ #include #include -#include +#include #include #include diff --git a/vtkm/worklet/testing/UnitTestContour.cxx b/vtkm/worklet/testing/UnitTestContour.cxx index e97405cdc..087d2adf2 100644 --- a/vtkm/worklet/testing/UnitTestContour.cxx +++ b/vtkm/worklet/testing/UnitTestContour.cxx @@ -16,7 +16,7 @@ #include #include -#include +#include #include #include From 130d0d9dfe9647b98c91a18d15de2547fea531c4 Mon Sep 17 00:00:00 2001 From: Li-Ta Lo Date: Fri, 3 Dec 2021 19:13:06 -0700 Subject: [PATCH 2/7] Updated Doxygen comments fixed some typo more Dosygen updates, remove necessary #include clarify on name lookup for overloaded virtural function minor refine on the name lookup rule rename subdirectory move virtual functions to .cxx, apply NVI pattern fixed EXPORT macro make namespace reflect directory structure --- vtkm/filter/CMakeLists.txt | 2 +- vtkm/filter/NewFilter.cxx | 15 +- vtkm/filter/NewFilter.h | 276 +++++++++++------- .../CMakeLists.txt | 19 +- .../GenerateIds.cxx | 12 +- .../GenerateIds.h | 11 +- .../testing/CMakeLists.txt | 2 +- .../testing/UnitTestGenerateIds.cxx | 9 +- vtkm/filter/testing/UnitTestContourFilter.cxx | 4 +- vtkm/worklet/testing/UnitTestContour.cxx | 6 +- 10 files changed, 209 insertions(+), 147 deletions(-) rename vtkm/filter/{FieldTransform => field_transform}/CMakeLists.txt (67%) rename vtkm/filter/{FieldTransform => field_transform}/GenerateIds.cxx (83%) rename vtkm/filter/{FieldTransform => field_transform}/GenerateIds.h (91%) rename vtkm/filter/{FieldTransform => field_transform}/testing/CMakeLists.txt (95%) rename vtkm/filter/{FieldTransform => field_transform}/testing/UnitTestGenerateIds.cxx (92%) diff --git a/vtkm/filter/CMakeLists.txt b/vtkm/filter/CMakeLists.txt index 3c8154ec6..9eee185a4 100644 --- a/vtkm/filter/CMakeLists.txt +++ b/vtkm/filter/CMakeLists.txt @@ -313,7 +313,7 @@ install(TARGETS vtkm_filter EXPORT ${VTKm_EXPORT_NAME}) add_subdirectory(internal) add_subdirectory(particleadvection) -add_subdirectory(FieldTransform) +add_subdirectory(field_transform) #-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - if (VTKm_ENABLE_TESTING) diff --git a/vtkm/filter/NewFilter.cxx b/vtkm/filter/NewFilter.cxx index c4d2c428c..659e65599 100644 --- a/vtkm/filter/NewFilter.cxx +++ b/vtkm/filter/NewFilter.cxx @@ -12,7 +12,6 @@ #include #include -#include #include #include @@ -38,6 +37,13 @@ void RunFilter(NewFilter* self, } } // anonymous namespace +NewFilter::~NewFilter() = default; + +bool NewFilter::CanThread() const +{ + return true; +} + //---------------------------------------------------------------------------- vtkm::cont::PartitionedDataSet NewFilter::DoExecute(const vtkm::cont::PartitionedDataSet& input) { @@ -76,6 +82,10 @@ vtkm::cont::PartitionedDataSet NewFilter::DoExecute(const vtkm::cont::Partitione 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) { @@ -84,13 +94,10 @@ vtkm::cont::PartitionedDataSet NewFilter::Execute(const vtkm::cont::PartitionedD (int)input.GetNumberOfPartitions(), vtkm::cont::TypeToString().c_str()); - // Call `void Derived::PreExecute(input, policy)`, if defined. this->PreExecute(input); - // Call `PrepareForExecution` (which should probably be renamed at some point) vtkm::cont::PartitionedDataSet output = this->DoExecute(input); - // Call `Derived::PostExecute(input, output, policy)` if defined. this->PostExecute(input, output); return output; diff --git a/vtkm/filter/NewFilter.h b/vtkm/filter/NewFilter.h index fe52fd9bc..fdbc009d9 100644 --- a/vtkm/filter/NewFilter.h +++ b/vtkm/filter/NewFilter.h @@ -10,19 +10,13 @@ #ifndef vtk_m_filter_NewFilter_h #define vtk_m_filter_NewFilter_h -#include #include -#include #include #include #include #include -#include #include -#include -#include -#include #include namespace vtkm @@ -33,7 +27,7 @@ namespace filter /// /// 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, -/// FilterDataSet, FilterDataSetWithField, etc. and implement relevant methods. +/// FilterParticleAdvection, etc.) and implement relevant methods. /// /// \section FilterUsage Usage /// @@ -63,53 +57,56 @@ namespace filter /// type, thus `Execute(DataSet&)` returns a DataSet while /// `Execute(PartitionedDataSet&)` returns a PartitionedDataSet. /// -/// The implementation for `Execute(DataSet&)` is merely provided for -/// convenience. Internally, it creates a PartitionedDataSet with a single -/// partition for the input and then forwards the call to -/// `Execute(PartitionedDataSet&)`. The method returns the first partition, if -/// any, from the PartitionedDataSet returned by the forwarded call. If the -/// PartitionedDataSet returned has more than 1 partition, then -/// `vtkm::cont::ErrorFilterExecution` will be thrown. +/// The pure virtual function `Execute(DataSet&)` is the main extension point of the +/// Filter interface. Filter developer needs to override `Execute(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 iterates DataSets of a PartitionedDataSet and pass +/// each individual DataSets to `Execute(DataSet&)`, possibly in a multi-threaded setting. +/// Developer of `Execute(DataSet&)` needs to indicate the thread-safeness of `Execute(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 +/// `Execute(PartitionedDataSet&)`. See the implementation of +/// `FilterParticleAdvection::Execute(PartitionedDataSet&)` for an example. /// /// \section FilterSubclassing Subclassing /// /// Typically, one subclasses one of the immediate subclasses of this class such as -/// FilterField, FilterDataSet, FilterDataSetWithField, etc. Those may impose +/// 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::Filter. /// /// \subsection FilterPreExecutePostExecute PreExecute and PostExecute /// -/// Subclasses may provide implementations for either or both of the following +/// Subclasses may provide implementations for either or both of the following protected /// methods. /// /// \code{cpp} /// -/// template -/// void PreExecute(const vtkm::cont::PartitionedDataSet& input, -/// const vtkm::filter::PolicyBase& policy); +/// void PreExecute(const vtkm::cont::PartitionedDataSet& input); /// -/// template -/// void PostExecute(const vtkm::cont::PartitionedDataSet& input, vtkm::cont::PartitionedDataSet& output -/// const vtkm::filter::PolicyBase& policy); +/// void PostExecute(const vtkm::cont::PartitionedDataSet& input, +/// vtkm::cont::PartitionedDataSet& output); /// /// \endcode /// -/// As the name suggests, these are called and the beginning and before the end -/// of an `Filter::Execute` call. Most filters that don't need to handle +/// As the name suggests, these are called and the before the beginning and after the end of +/// iterative `Filter::Execute(DataSet&)` calls. Most filters that don't need to handle /// PartitionedDataSet specially, e.g. clip, cut, iso-contour, need not worry /// about these methods or provide any implementation. If, however, your filter -/// needs do to some initialization e.g. allocation buffers to accumulate +/// needs to do some initialization e.g. allocation buffers to accumulate /// results, or finalization e.g. reduce results across all partitions, then /// these methods provide convenient hooks for the same. /// -/// \subsection FilterPrepareForExecution PrepareForExecution +/// \subsection FilterExecution Execute /// -/// A concrete subclass of Filter must provide `PrepareForExecution` -/// 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. +/// A concrete subclass of Filter must provide `Execute` 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 @@ -120,10 +117,7 @@ namespace filter /// /// \code{cpp} /// -/// template -/// vtkm::cont::DataSet PrepareForExecution( -/// const vtkm::cont::DataSet& input, -/// const vtkm::filter::PolicyBase& policy); +/// vtkm::cont::DataSet Execution(const vtkm::cont::DataSet& input); /// /// \endcode /// @@ -131,9 +125,9 @@ namespace filter /// result and return it. If there are any errors, the subclass must throw an /// exception (e.g. `vtkm::cont::ErrorFilterExecution`). /// -/// In this case, the Filter superclass handles iterating over multiple +/// In this simple case, the Filter superclass handles iterating over multiple /// partitions in the input PartitionedDataSet and calling -/// `PrepareForExecution` iteratively. +/// `Execute(DataSet&)` iteratively. /// /// The aforementioned approach is also suitable for filters that need special /// handling for PartitionedDataSets which can be modelled as PreExecute and @@ -144,42 +138,136 @@ namespace filter /// results, one can implement the following signature. /// /// \code{cpp} -/// template -/// vtkm::cont::PartitionedDataSet PrepareForExecution( -/// const vtkm::cont::PartitionedDataSet& input, -/// const vtkm::filter::PolicyBase& policy); +/// vtkm::cont::PartitionedDataSet Execute( +/// const vtkm::cont::PartitionedDataSet& input); /// \endcode /// /// The responsibility of this method is the same, except now the subclass is /// given full control over the execution, including any mapping of fields to /// output (described in next sub-section). /// -/// \subsection FilterMapFieldOntoOutput DoMapField +/// \subsection FilterMappingFields MapFieldsOntoOutput /// -/// Subclasses may provide `DoMapField` method with the following -/// signature: +/// For subclasses that map input fields into output fields, the implementation of its +/// `Execute(DataSet&)` should call `Filter::MapFieldsOntoOutput` with a properly defined +/// `Mapper`, before returning the output DataSet. For example: /// /// \code{cpp} +/// VTKM_CONT DataSet SomeFilter::Execute(const vtkm::cont::DataSet& input) +/// { +/// vtkm::cont::DataSet output; +/// output = ... // Generation of the new DataSet /// -/// template -/// VTKM_CONT bool DoMapField(vtkm::cont::DataSet& result, -/// const vtkm::cont::Field& field, -/// const vtkm::filter::PolicyBase& policy); +/// // 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 /// -/// When present, this method will be called after each partition execution to -/// map an input field from the corresponding input partition to the output -/// partition. +/// `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 add input Field to +/// output DaaSet (via a shallow copy). /// +/// \subsection FilterThreadSafety CanThread +/// +/// By default, the implementation of `Execute(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 `Execute(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 `Execute(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 +/// `Execute(DataSet&)`. For example: +/// +/// \code{cpp} +/// struct SharedState; // shared states between mesh generation and field mapping. +/// VTKM_CONT DataSet ThreadSafeFilter::Execute(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 +/// `DoExecute(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. +/// +/// \subsection FilterNameLookup Overriding Overloaded Functions +/// Since we have two overloads of `Execute`, we need to work with C++'s rule for name lookup for +/// inherited, overloaded functions when overriding them. In most uses cases, we intend to only +/// override the `Execute(DataSet&)` overload in an implementation of a NewFilter subclass, such as +/// +/// \code{cpp} +/// class FooFilter : public NewFilter +/// { +/// ... +/// vtkm::cont::DataSet Execute(const vtkm::cont::DataSet& input) override; +/// ... +/// } +/// \endcode +/// +/// However, the compiler will stop the name lookup process once it sees the +/// `FooFilter::Execute(DataSet)`. When a user calls `FooFilter::Execute(PartitionedDataSet&)`, +/// the compiler will not find the overload from the base class `NewFilter`, resulting in failed +/// overload resolution. The solution to such a problem is to use a using-declaration in the +/// subclass definition to bring the `NewFilter::Execute(PartitionedDataSet&)` into scope for +/// name lookup. For example: +/// +/// \code{cpp} +/// class FooFilter : public NewFilter +/// { +/// ... +/// using vtkm::filter::NewFilter::Execute; // bring overloads of Execute into name lookup +/// vtkm::cont::DataSet Execute(const vtkm::cont::DataSet& input) override; +/// ... +/// } +/// \endcode class VTKM_FILTER_CORE_EXPORT NewFilter { public: VTKM_CONT - virtual ~NewFilter() = default; + virtual ~NewFilter(); VTKM_CONT - virtual bool CanThread() const { return true; } + virtual bool CanThread() const; VTKM_CONT bool GetRunMultiThreadedFilter() const @@ -205,36 +293,6 @@ public: /// A filter is able to state what subset of types it supports. using SupportedTypes = VTKM_DEFAULT_TYPE_LIST; - /// \brief Specify which additional field storage to support. - /// - /// When a filter gets a field value from a DataSet, it has to determine what type - /// of storage the array has. Typically this is taken from the default storage - /// types defined in DefaultTypes.h. In some cases it is useful to support additional - /// types. For example, the filter might make sense to support ArrayHandleIndex or - /// ArrayHandleConstant. If so, the storage of those additional types should be - /// listed here. - using AdditionalFieldStorage = vtkm::ListEmpty; - - /// \brief Specify which structured cell sets to support. - /// - /// When a filter gets a cell set from a DataSet, it has to determine what type - /// of concrete cell set it is. This provides a list of supported structured - /// cell sets. - using SupportedStructuredCellSets = VTKM_DEFAULT_CELL_SET_LIST_STRUCTURED; - - /// \brief Specify which unstructured cell sets to support. - /// - /// When a filter gets a cell set from a DataSet, it has to determine what type - /// of concrete cell set it is. This provides a list of supported unstructured - /// cell sets. - using SupportedUnstructuredCellSets = VTKM_DEFAULT_CELL_SET_LIST_UNSTRUCTURED; - - /// \brief Specify which unstructured cell sets to support. - /// - /// When a filter gets a cell set from a DataSet, it has to determine what type - /// of concrete cell set it is. This provides a list of supported cell sets. - using SupportedCellSets = VTKM_DEFAULT_CELL_SET_LIST; - //@{ /// \brief Specify which fields get passed from input to output. /// @@ -289,15 +347,14 @@ public: /// 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 virtual vtkm::cont::DataSet Execute(const vtkm::cont::DataSet& input) = 0; + 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 virtual vtkm::cont::PartitionedDataSet Execute( - const vtkm::cont::PartitionedDataSet& input); + VTKM_CONT vtkm::cont::PartitionedDataSet Execute(const vtkm::cont::PartitionedDataSet& input); //@} // FIXME: Is this actually materialize? Are there different kinds of Invoker? @@ -306,40 +363,15 @@ public: /// which device adapters a filter uses. void SetInvoker(vtkm::cont::Invoker inv) { this->Invoke = inv; } - // TODO: de-virtual, move to protected. - VTKM_CONT - virtual vtkm::Id DetermineNumberOfThreads(const vtkm::cont::PartitionedDataSet& input); - protected: vtkm::cont::Invoker Invoke; vtkm::Id CoordinateSystemIndex = 0; - //@{ - /// when operating on vtkm::cont::PartitionedDataSet, we - /// want to do processing across ranks as well. Just adding pre/post handles - /// for the same does the trick. - VTKM_CONT virtual void PreExecute(const vtkm::cont::PartitionedDataSet&) {} - - VTKM_CONT virtual void PostExecute(const vtkm::cont::PartitionedDataSet&, - vtkm::cont::PartitionedDataSet&) - { - } - //@} - - VTKM_CONT virtual vtkm::cont::PartitionedDataSet DoExecute( - const vtkm::cont::PartitionedDataSet& inData); - - static void defaultMapper(vtkm::cont::DataSet& output, const vtkm::cont::Field& field) - { - output.AddField(field); - }; - template VTKM_CONT void MapFieldsOntoOutput(const vtkm::cont::DataSet& input, vtkm::cont::DataSet& output, Mapper&& mapper) { - // TODO: in the future of C++20, we can make it a "filtered_view". for (vtkm::IdComponent cc = 0; cc < input.GetNumberOfFields(); ++cc) { auto field = input.GetField(cc); @@ -356,6 +388,30 @@ protected: } private: + VTKM_CONT + virtual vtkm::Id DetermineNumberOfThreads(const vtkm::cont::PartitionedDataSet& input); + + //@{ + /// when operating on vtkm::cont::PartitionedDataSet, we + /// want to do processing across ranks as well. Just adding pre/post handles + /// for the same does the trick. + VTKM_CONT virtual void PreExecute(const vtkm::cont::PartitionedDataSet&) {} + + VTKM_CONT virtual void PostExecute(const vtkm::cont::PartitionedDataSet&, + vtkm::cont::PartitionedDataSet&) + { + } + //@} + + VTKM_CONT virtual vtkm::cont::DataSet DoExecute(const vtkm::cont::DataSet& inData) = 0; + VTKM_CONT virtual vtkm::cont::PartitionedDataSet DoExecute( + 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; }; diff --git a/vtkm/filter/FieldTransform/CMakeLists.txt b/vtkm/filter/field_transform/CMakeLists.txt similarity index 67% rename from vtkm/filter/FieldTransform/CMakeLists.txt rename to vtkm/filter/field_transform/CMakeLists.txt index c6ddf467c..f9eec9bbc 100644 --- a/vtkm/filter/FieldTransform/CMakeLists.txt +++ b/vtkm/filter/field_transform/CMakeLists.txt @@ -7,25 +7,20 @@ ## the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR ## PURPOSE. See the above copyright notice for more information. ##============================================================================ -set(fieldtransform_headers +set(fieldt_ransform_headers GenerateIds.h) -set(fieldtransform_sources_device +set(field_transform_sources_device GenerateIds.cxx) vtkm_library( - NAME vtkm_filter_fieldtransform - HEADERS ${fieldtransform_headers} - DEVICE_SOURCES ${fieldtransform_sources_device} + NAME vtkm_filter_field_transform + HEADERS ${field_transform_headers} + DEVICE_SOURCES ${field_transform_sources_device} USE_VTKM_JOB_POOL ) -set_property(TARGET - vtkm_filter_fieldtransform - PROPERTY UNITY_BUILD_MODE GROUP - ) - -target_link_libraries(vtkm_filter_fieldtransform PUBLIC vtkm_worklet vtkm_filter_core) -target_link_libraries(vtkm_filter PUBLIC INTERFACE vtkm_filter_fieldtransform) +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) diff --git a/vtkm/filter/FieldTransform/GenerateIds.cxx b/vtkm/filter/field_transform/GenerateIds.cxx similarity index 83% rename from vtkm/filter/FieldTransform/GenerateIds.cxx rename to vtkm/filter/field_transform/GenerateIds.cxx index b9fe9b0e3..f1296f14d 100644 --- a/vtkm/filter/FieldTransform/GenerateIds.cxx +++ b/vtkm/filter/field_transform/GenerateIds.cxx @@ -9,12 +9,13 @@ //============================================================================ #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); @@ -38,8 +39,9 @@ namespace vtkm { namespace filter { - -vtkm::cont::DataSet GenerateIds::Execute(const vtkm::cont::DataSet& input) +namespace field_transform +{ +vtkm::cont::DataSet GenerateIds::DoExecute(const vtkm::cont::DataSet& input) { vtkm::cont::DataSet output = input; @@ -58,6 +60,6 @@ vtkm::cont::DataSet GenerateIds::Execute(const vtkm::cont::DataSet& input) return output; } - +} // namespace field_transform } // namespace vtkm::filter } // namespace vtkm diff --git a/vtkm/filter/FieldTransform/GenerateIds.h b/vtkm/filter/field_transform/GenerateIds.h similarity index 91% rename from vtkm/filter/FieldTransform/GenerateIds.h rename to vtkm/filter/field_transform/GenerateIds.h index 8b03b49c1..284d450cd 100644 --- a/vtkm/filter/FieldTransform/GenerateIds.h +++ b/vtkm/filter/field_transform/GenerateIds.h @@ -10,14 +10,15 @@ #ifndef vtk_m_filter_GenerateIds_h #define vtk_m_filter_GenerateIds_h -#include #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 @@ -27,7 +28,7 @@ namespace filter /// convenient for adding indices to operations designed for fields and generally /// creating test data. /// -class VTKM_FILTER_FIELDTRANSFORM_EXPORT GenerateIds : public vtkm::filter::NewFilter +class VTKM_FILTER_FIELD_TRANSFORM_EXPORT GenerateIds : public vtkm::filter::NewFilter { std::string PointFieldName = "pointids"; std::string CellFieldName = "cellids"; @@ -84,9 +85,9 @@ public: bool GetUseFloat() const { return this->UseFloat; } void SetUseFloat(bool flag) { this->UseFloat = flag; } - vtkm::cont::DataSet Execute(const vtkm::cont::DataSet& input) override; + vtkm::cont::DataSet DoExecute(const vtkm::cont::DataSet& input) override; }; - +} // namespace field_transform } // namespace vtkm::filter } // namespace vtkm diff --git a/vtkm/filter/FieldTransform/testing/CMakeLists.txt b/vtkm/filter/field_transform/testing/CMakeLists.txt similarity index 95% rename from vtkm/filter/FieldTransform/testing/CMakeLists.txt rename to vtkm/filter/field_transform/testing/CMakeLists.txt index fd25171c6..8b1d72d89 100644 --- a/vtkm/filter/FieldTransform/testing/CMakeLists.txt +++ b/vtkm/filter/field_transform/testing/CMakeLists.txt @@ -12,7 +12,7 @@ set(unit_tests UnitTestGenerateIds.cxx) set(libraries - vtkm_filter_fieldtransform + vtkm_filter_field_transform vtkm_source) vtkm_unit_tests( diff --git a/vtkm/filter/FieldTransform/testing/UnitTestGenerateIds.cxx b/vtkm/filter/field_transform/testing/UnitTestGenerateIds.cxx similarity index 92% rename from vtkm/filter/FieldTransform/testing/UnitTestGenerateIds.cxx rename to vtkm/filter/field_transform/testing/UnitTestGenerateIds.cxx index 91f05fe07..312dad2df 100644 --- a/vtkm/filter/FieldTransform/testing/UnitTestGenerateIds.cxx +++ b/vtkm/filter/field_transform/testing/UnitTestGenerateIds.cxx @@ -9,7 +9,7 @@ //============================================================================ #include #include -#include +#include #include namespace @@ -34,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()); @@ -77,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/UnitTestContourFilter.cxx b/vtkm/filter/testing/UnitTestContourFilter.cxx index 7356dfbe3..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 087d2adf2..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"); From 0e3cc807679644a3918f04961ba224ae4d076c41 Mon Sep 17 00:00:00 2001 From: Li-Ta Lo Date: Thu, 9 Dec 2021 17:28:44 -0700 Subject: [PATCH 3/7] restore deprecated filter::GenerateIds update comments privatize DoExecute remove Pre|PostExecute more Eecute -> DoExecute changes fixed a typo so GeneratedIds.h gets installed --- vtkm/filter/CMakeLists.txt | 1 + vtkm/filter/GenerateIds.h | 40 +++++ vtkm/filter/NewFilter.cxx | 6 +- vtkm/filter/NewFilter.h | 198 +++++++-------------- vtkm/filter/field_transform/CMakeLists.txt | 2 +- vtkm/filter/field_transform/GenerateIds.h | 7 +- vtkm/worklet/testing/UnitTestContour.cxx | 4 +- 7 files changed, 118 insertions(+), 140 deletions(-) create mode 100644 vtkm/filter/GenerateIds.h diff --git a/vtkm/filter/CMakeLists.txt b/vtkm/filter/CMakeLists.txt index 9eee185a4..bb78c89a3 100644 --- a/vtkm/filter/CMakeLists.txt +++ b/vtkm/filter/CMakeLists.txt @@ -89,6 +89,7 @@ set(extra_headers ExternalFaces.h FieldSelection.h FieldToColors.h + GenerateIds.h GhostCellClassify.h GhostCellRemove.h Histogram.h diff --git a/vtkm/filter/GenerateIds.h b/vtkm/filter/GenerateIds.h new file mode 100644 index 000000000..7af613132 --- /dev/null +++ b/vtkm/filter/GenerateIds.h @@ -0,0 +1,40 @@ +//============================================================================ +// 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_GenerateIds_h +#define vtk_m_filter_GenerateIds_h + +#include +#include + +namespace vtkm +{ +namespace 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() +{ + GenerateIds_deprecated(); +} + +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 + +#endif //vtk_m_filter_GenerateIds_h diff --git a/vtkm/filter/NewFilter.cxx b/vtkm/filter/NewFilter.cxx index 659e65599..c4cf2dded 100644 --- a/vtkm/filter/NewFilter.cxx +++ b/vtkm/filter/NewFilter.cxx @@ -82,6 +82,7 @@ vtkm::cont::PartitionedDataSet NewFilter::DoExecute(const vtkm::cont::Partitione return output; } + vtkm::cont::DataSet NewFilter::Execute(const vtkm::cont::DataSet& input) { return this->DoExecute(input); @@ -94,12 +95,7 @@ vtkm::cont::PartitionedDataSet NewFilter::Execute(const vtkm::cont::PartitionedD (int)input.GetNumberOfPartitions(), vtkm::cont::TypeToString().c_str()); - this->PreExecute(input); - vtkm::cont::PartitionedDataSet output = this->DoExecute(input); - - this->PostExecute(input, output); - return output; } diff --git a/vtkm/filter/NewFilter.h b/vtkm/filter/NewFilter.h index fdbc009d9..bb5431613 100644 --- a/vtkm/filter/NewFilter.h +++ b/vtkm/filter/NewFilter.h @@ -25,135 +25,116 @@ 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. +/// 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: +/// 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. MarchingCubes). -/// vtkm::filter::MarchingCubes marchingCubes; +/// // create the concrete subclass (e.g. Contour). +/// vtkm::filter::contour::Contour contour; /// -/// // select fieds to map to the output, if different from default which is to -/// // map all input fields. -/// marchingCubes.SetFieldToPass({"var1", "var2"}); +/// // 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 = filter.Execute(dsInput); +/// auto outputDS = contour.Execute(dsInput); /// /// // or, execute on a vtkm::cont::PartitionedDataSet /// vtkm::cont::PartitionedDataSet mbInput = ... -/// auto outputMB = filter.Execute(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` 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. /// -/// The pure virtual function `Execute(DataSet&)` is the main extension point of the -/// Filter interface. Filter developer needs to override `Execute(DataSet)` to implement -/// the business logic of filtering operations on a single DataSet. +/// `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 iterates DataSets of a PartitionedDataSet and pass -/// each individual DataSets to `Execute(DataSet&)`, possibly in a multi-threaded setting. -/// Developer of `Execute(DataSet&)` needs to indicate the thread-safeness of `Execute(DataSet&)` -/// by overriding the `CanThread()` virtual method which by default returns `true`. +/// convenience. Internally, it calls `DoExecute(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 -/// `Execute(PartitionedDataSet&)`. See the implementation of +/// `DoExecute(PartitionedDataSet&)`. See the implementation of /// `FilterParticleAdvection::Execute(PartitionedDataSet&)` for an example. /// /// \section FilterSubclassing Subclassing /// -/// Typically, 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::Filter. -/// -/// \subsection FilterPreExecutePostExecute PreExecute and PostExecute -/// -/// Subclasses may provide implementations for either or both of the following protected -/// methods. -/// -/// \code{cpp} -/// -/// void PreExecute(const vtkm::cont::PartitionedDataSet& input); -/// -/// void PostExecute(const vtkm::cont::PartitionedDataSet& input, -/// vtkm::cont::PartitionedDataSet& output); -/// -/// \endcode -/// -/// As the name suggests, these are called and the before the beginning and after the end of -/// iterative `Filter::Execute(DataSet&)` calls. Most filters that don't need to handle -/// PartitionedDataSet specially, e.g. clip, cut, iso-contour, need not worry -/// about these methods or provide any implementation. If, however, your filter -/// needs to do some initialization e.g. allocation buffers to accumulate -/// results, or finalization e.g. reduce results across all partitions, then -/// these methods provide convenient hooks for the same. +/// 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 `Execute` 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. +/// 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. +/// 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 Execution(const vtkm::cont::DataSet& input); +/// 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`). +/// 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 Filter superclass handles iterating over multiple -/// partitions in the input PartitionedDataSet and calling -/// `Execute(DataSet&)` iteratively. +/// 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 which can be modelled as PreExecute and -/// PostExecute steps (e.g. `vtkm::filter::Histogram`). -/// -/// For more complex filters, like streamlines, particle tracking, where the -/// processing of PartitionedDataSets cannot be modelled as a reduction of the -/// results, one can implement the following signature. +/// 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 `DoExecute(PartitionedDataSet&)` while calling to the base class +/// `DoExecute(PartitionedDataSet&) as helper function for iteration on DataSets. /// /// \code{cpp} -/// vtkm::cont::PartitionedDataSet Execute( -/// const vtkm::cont::PartitionedDataSet& input); +/// vtkm::cont::PartitionedDataSet FooFilter::DoExecute( +/// const vtkm::cont::PartitionedDataSet& input) +/// { +/// // Do pre execute stuff, e.g. scattering to each DataSet +/// auto output = this->NewFilter::DoExecute(input); +/// // Do post execute stuff, e.g gather/reduce from DataSets +/// return output; +/// } /// \endcode /// -/// The responsibility of this method is the same, except now the subclass is -/// given full control over the execution, including any mapping of fields to -/// output (described in next sub-section). +/// 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 `DoExecute(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 -/// `Execute(DataSet&)` should call `Filter::MapFieldsOntoOutput` with a properly defined +/// `DoExecute(DataSet&)` should call `NewFilter::MapFieldsOntoOutput` with a properly defined /// `Mapper`, before returning the output DataSet. For example: /// /// \code{cpp} -/// VTKM_CONT DataSet SomeFilter::Execute(const vtkm::cont::DataSet& input) +/// VTKM_CONT DataSet SomeFilter::DoExecute(const vtkm::cont::DataSet& input) /// { /// vtkm::cont::DataSet output; /// output = ... // Generation of the new DataSet @@ -174,28 +155,28 @@ namespace filter /// 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 add input Field to +/// 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 `Execute(DataSet&)` should model a *pure function*, i.e. it +/// 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 `Execute(PartitionedDataSet&)` to be simply a parallel for-each, +/// the default implementation of `DoExecute(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 `Execute(DataSet&)`, we have eliminated most +/// 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 -/// `Execute(DataSet&)`. For example: +/// `DoExecute(DataSet&)`. For example: /// /// \code{cpp} /// struct SharedState; // shared states between mesh generation and field mapping. -/// VTKM_CONT DataSet ThreadSafeFilter::Execute(const vtkm::cont::DataSet& input) +/// 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 @@ -230,36 +211,6 @@ namespace filter /// Implementations of Filter subclass can also override /// `DetermineNumberOfThreads()` to provide implementation specific heuristic. /// -/// \subsection FilterNameLookup Overriding Overloaded Functions -/// Since we have two overloads of `Execute`, we need to work with C++'s rule for name lookup for -/// inherited, overloaded functions when overriding them. In most uses cases, we intend to only -/// override the `Execute(DataSet&)` overload in an implementation of a NewFilter subclass, such as -/// -/// \code{cpp} -/// class FooFilter : public NewFilter -/// { -/// ... -/// vtkm::cont::DataSet Execute(const vtkm::cont::DataSet& input) override; -/// ... -/// } -/// \endcode -/// -/// However, the compiler will stop the name lookup process once it sees the -/// `FooFilter::Execute(DataSet)`. When a user calls `FooFilter::Execute(PartitionedDataSet&)`, -/// the compiler will not find the overload from the base class `NewFilter`, resulting in failed -/// overload resolution. The solution to such a problem is to use a using-declaration in the -/// subclass definition to bring the `NewFilter::Execute(PartitionedDataSet&)` into scope for -/// name lookup. For example: -/// -/// \code{cpp} -/// class FooFilter : public NewFilter -/// { -/// ... -/// using vtkm::filter::NewFilter::Execute; // bring overloads of Execute into name lookup -/// vtkm::cont::DataSet Execute(const vtkm::cont::DataSet& input) override; -/// ... -/// } -/// \endcode class VTKM_FILTER_CORE_EXPORT NewFilter { public: @@ -391,18 +342,7 @@ private: VTKM_CONT virtual vtkm::Id DetermineNumberOfThreads(const vtkm::cont::PartitionedDataSet& input); - //@{ - /// when operating on vtkm::cont::PartitionedDataSet, we - /// want to do processing across ranks as well. Just adding pre/post handles - /// for the same does the trick. - VTKM_CONT virtual void PreExecute(const vtkm::cont::PartitionedDataSet&) {} - - VTKM_CONT virtual void PostExecute(const vtkm::cont::PartitionedDataSet&, - vtkm::cont::PartitionedDataSet&) - { - } - //@} - + // 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 DoExecute( const vtkm::cont::PartitionedDataSet& inData); diff --git a/vtkm/filter/field_transform/CMakeLists.txt b/vtkm/filter/field_transform/CMakeLists.txt index f9eec9bbc..e488a2421 100644 --- a/vtkm/filter/field_transform/CMakeLists.txt +++ b/vtkm/filter/field_transform/CMakeLists.txt @@ -7,7 +7,7 @@ ## the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR ## PURPOSE. See the above copyright notice for more information. ##============================================================================ -set(fieldt_ransform_headers +set(field_transform_headers GenerateIds.h) set(field_transform_sources_device GenerateIds.cxx) diff --git a/vtkm/filter/field_transform/GenerateIds.h b/vtkm/filter/field_transform/GenerateIds.h index 284d450cd..1183c46a0 100644 --- a/vtkm/filter/field_transform/GenerateIds.h +++ b/vtkm/filter/field_transform/GenerateIds.h @@ -7,8 +7,8 @@ // the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR // PURPOSE. See the above copyright notice for more information. //============================================================================ -#ifndef vtk_m_filter_GenerateIds_h -#define vtk_m_filter_GenerateIds_h +#ifndef vtk_m_filter_field_transform_GenerateIds_h +#define vtk_m_filter_field_transform_GenerateIds_h #include #include @@ -85,10 +85,11 @@ public: 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_GenerateIds_h +#endif //vtk_m_filter_field_transform_GenerateIds_h diff --git a/vtkm/worklet/testing/UnitTestContour.cxx b/vtkm/worklet/testing/UnitTestContour.cxx index 5d2906fe6..33e56c168 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::field_transform::GenerateIds genIds; + vtkm::filter::GenerateIds genIds; genIds.SetUseFloat(true); genIds.SetGeneratePointIds(false); genIds.SetCellFieldName("cellvar"); From 161391656ad7e5f5c50debc51d8adda49ef50899 Mon Sep 17 00:00:00 2001 From: Li-Ta Lo Date: Mon, 13 Dec 2021 09:23:35 -0700 Subject: [PATCH 4/7] Add ChangeLog --- docs/changelog/NewFitlerInterface.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 docs/changelog/NewFitlerInterface.md diff --git a/docs/changelog/NewFitlerInterface.md b/docs/changelog/NewFitlerInterface.md new file mode 100644 index 000000000..fcd0cf56c --- /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 compiler 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 Docygen documentation in +NewFilter.h + +Filter implementrations are also re-orgnized into submodules, with each submodile +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 submoudle 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. From 74905a05cdd7a611cc59f635e012370703f2e71e Mon Sep 17 00:00:00 2001 From: Li-Ta Lo Date: Mon, 13 Dec 2021 10:41:42 -0700 Subject: [PATCH 5/7] use new header file --- vtkm/worklet/testing/UnitTestContour.cxx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vtkm/worklet/testing/UnitTestContour.cxx b/vtkm/worklet/testing/UnitTestContour.cxx index 33e56c168..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"); From 1469d34b4353246e1d6d4dd761d48096b68fc88d Mon Sep 17 00:00:00 2001 From: Li-Ta Lo Date: Mon, 13 Dec 2021 12:17:15 -0700 Subject: [PATCH 6/7] rename DoExecutePartitions --- docs/changelog/NewFitlerInterface.md | 8 ++++---- vtkm/filter/NewFilter.cxx | 5 +++-- vtkm/filter/NewFilter.h | 27 ++++++++++++++------------- 3 files changed, 21 insertions(+), 19 deletions(-) diff --git a/docs/changelog/NewFitlerInterface.md b/docs/changelog/NewFitlerInterface.md index fcd0cf56c..1785b8937 100644 --- a/docs/changelog/NewFitlerInterface.md +++ b/docs/changelog/NewFitlerInterface.md @@ -2,7 +2,7 @@ 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 compiler every single Filter users with a Device Compiler. +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. @@ -12,9 +12,9 @@ implementations are encouraged to take advantage of the new design and removing shared metatable states from their `DoExecute`, see Docygen documentation in NewFilter.h -Filter implementrations are also re-orgnized into submodules, with each submodile +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 submoudle library file, for example, `libvtkm_filter_field_transform.so`. +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/NewFilter.cxx b/vtkm/filter/NewFilter.cxx index c4cf2dded..c893e28aa 100644 --- a/vtkm/filter/NewFilter.cxx +++ b/vtkm/filter/NewFilter.cxx @@ -45,7 +45,8 @@ bool NewFilter::CanThread() const } //---------------------------------------------------------------------------- -vtkm::cont::PartitionedDataSet NewFilter::DoExecute(const vtkm::cont::PartitionedDataSet& input) +vtkm::cont::PartitionedDataSet NewFilter::DoExecutePartitions( + const vtkm::cont::PartitionedDataSet& input) { vtkm::cont::PartitionedDataSet output; @@ -95,7 +96,7 @@ vtkm::cont::PartitionedDataSet NewFilter::Execute(const vtkm::cont::PartitionedD (int)input.GetNumberOfPartitions(), vtkm::cont::TypeToString().c_str()); - vtkm::cont::PartitionedDataSet output = this->DoExecute(input); + vtkm::cont::PartitionedDataSet output = this->DoExecutePartitions(input); return output; } diff --git a/vtkm/filter/NewFilter.h b/vtkm/filter/NewFilter.h index bb5431613..06ba81730 100644 --- a/vtkm/filter/NewFilter.h +++ b/vtkm/filter/NewFilter.h @@ -62,7 +62,7 @@ namespace filter /// DataSet. /// /// The default implementation of `Execute(PartitionedDataSet&)` is merely provided for -/// convenience. Internally, it calls `DoExecute(PartitionedDataSet)` to iterate DataSets +/// 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 @@ -70,7 +70,7 @@ namespace filter /// /// 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 -/// `DoExecute(PartitionedDataSet&)`. See the implementation of +/// `DoExecutePartitions(PartitionedDataSet&)`. See the implementation of /// `FilterParticleAdvection::Execute(PartitionedDataSet&)` for an example. /// /// \section FilterSubclassing Subclassing @@ -108,15 +108,15 @@ namespace filter /// 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 `DoExecute(PartitionedDataSet&)` while calling to the base class -/// `DoExecute(PartitionedDataSet&) as helper function for iteration on DataSets. +/// overriding `DoExecutePartitions(PartitionedDataSet&)` while calling to the base class +/// `DoExecutePartitions(PartitionedDataSet&) as helper function for iteration on DataSets. /// /// \code{cpp} -/// vtkm::cont::PartitionedDataSet FooFilter::DoExecute( +/// vtkm::cont::PartitionedDataSet FooFilter::DoExecutePartitions( /// const vtkm::cont::PartitionedDataSet& input) /// { /// // Do pre execute stuff, e.g. scattering to each DataSet -/// auto output = this->NewFilter::DoExecute(input); +/// auto output = this->NewFilter::DoExecutePartitions(input); /// // Do post execute stuff, e.g gather/reduce from DataSets /// return output; /// } @@ -124,8 +124,9 @@ namespace filter /// /// 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 `DoExecute(PartitionedDataSet&)`. Now the subclass is given full control -/// over the execution, including any mapping of fields to output (described in next sub-section). +/// 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 /// @@ -162,8 +163,8 @@ namespace filter /// /// 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 `DoExecute(PartitionedDataSet&)` to be simply a parallel for-each, -/// thus facilitates multi-threaded execution without any lock. +/// 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 @@ -205,8 +206,8 @@ namespace filter /// \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 -/// `DoExecute(PartitionedDataSet)` virtual method to provide implementation specific scheduling -/// policy. The default number of *worker* threads in the pool are determined by 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. @@ -344,7 +345,7 @@ private: // 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 DoExecute( + VTKM_CONT virtual vtkm::cont::PartitionedDataSet DoExecutePartitions( const vtkm::cont::PartitionedDataSet& inData); static void defaultMapper(vtkm::cont::DataSet& output, const vtkm::cont::Field& field) From ba2a710e7fdd870f0d056235b130bd2b69e5b8a7 Mon Sep 17 00:00:00 2001 From: Li-Ta Lo Date: Mon, 13 Dec 2021 16:31:29 -0500 Subject: [PATCH 7/7] Update NewFitlerInterface.md --- docs/changelog/NewFitlerInterface.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changelog/NewFitlerInterface.md b/docs/changelog/NewFitlerInterface.md index 1785b8937..d5e596245 100644 --- a/docs/changelog/NewFitlerInterface.md +++ b/docs/changelog/NewFitlerInterface.md @@ -9,7 +9,7 @@ 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 Docygen documentation in +shared metatable states from their `DoExecute`, see Doxygen documentation in NewFilter.h Filter implementations are also re-organized into submodules, with each submodule