//============================================================================ // 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_ArrayHandleDecorator_h #define vtk_m_ArrayHandleDecorator_h #include #include #include #include #include #include #include #include #include #include #include #include #include namespace vtkm { namespace internal { namespace decor { // Generic InverseFunctor implementation that does nothing. struct NoOpInverseFunctor { NoOpInverseFunctor() = default; template VTKM_EXEC_CONT NoOpInverseFunctor(Ts...) { } template VTKM_EXEC_CONT void operator()(vtkm::Id, VT) const { } }; } // namespace decor // The portal for ArrayHandleDecorator. Get calls FunctorType::operator(), and // Set calls InverseFunctorType::operator(), but only if the DecoratorImpl // provides an inverse. template class VTKM_ALWAYS_EXPORT ArrayPortalDecorator { public: using ValueType = ValueType_; using FunctorType = FunctorType_; using InverseFunctorType = InverseFunctorType_; using ReadOnly = std::is_same; VTKM_EXEC_CONT ArrayPortalDecorator() {} VTKM_CONT ArrayPortalDecorator(FunctorType func, InverseFunctorType iFunc, vtkm::Id numValues) : Functor(func) , InverseFunctor(iFunc) , NumberOfValues(numValues) { } VTKM_EXEC_CONT vtkm::Id GetNumberOfValues() const { return this->NumberOfValues; } VTKM_EXEC_CONT ValueType Get(vtkm::Id index) const { return this->Functor(index); } template ::type> VTKM_EXEC_CONT void Set(vtkm::Id index, const ValueType& value) const { this->InverseFunctor(index, value); } private: FunctorType Functor; InverseFunctorType InverseFunctor; vtkm::Id NumberOfValues; }; } } // namespace vtkm::internal namespace vtkm { namespace cont { namespace internal { namespace decor { // Ensures that all types in variadic container ArrayHandleList are subclasses // of ArrayHandleBase. template using AllAreArrayHandles = vtkm::ListAll; namespace detail { // Tests whether DecoratorImplT has a CreateInverseFunctor(Portals...) method. template struct IsFunctorInvertibleImpl; template class List, typename... PortalTs> struct IsFunctorInvertibleImpl> { private: template < typename T, typename U = decltype(std::declval().CreateInverseFunctor(std::declval()...))> static std::true_type InverseExistsTest(int); template static std::false_type InverseExistsTest(...); public: using type = decltype(InverseExistsTest(0)); }; // Tests whether DecoratorImplT has an AllocateSourceArrays(size, Arrays...) method. template struct IsDecoratorAllocatableImpl; template class List, typename... ArrayTs> struct IsDecoratorAllocatableImpl> { private: template < typename T, typename U = decltype(std::declval().AllocateSourceArrays(0, vtkm::CopyFlag::Off, std::declval(), std::declval()...))> static std::true_type Exists(int); template static std::false_type Exists(...); public: using type = decltype(Exists(0)); }; // Deduces the type returned by DecoratorImplT::CreateFunctor when given // the specified portals. template struct GetFunctorTypeImpl; template class List, typename... PortalTs> struct GetFunctorTypeImpl> { using type = decltype(std::declval().CreateFunctor(std::declval()...)); }; // Deduces the type returned by DecoratorImplT::CreateInverseFunctor when given // the specified portals. If DecoratorImplT doesn't have a CreateInverseFunctor // method, a NoOp functor will be used instead. template struct GetInverseFunctorTypeImpl; template class List, typename... PortalTs> struct GetInverseFunctorTypeImpl> { using type = decltype(std::declval().CreateInverseFunctor(std::declval()...)); }; template struct GetInverseFunctorTypeImpl { using type = vtkm::internal::decor::NoOpInverseFunctor; }; // Get appropriate portals from a source array. // See note below about using non-writable portals in invertible functors. // We need to sub in const portals when writable ones don't exist. template typename std::decay::type::WritePortalType GetWritePortalImpl( std::true_type, ArrayT&& array, vtkm::cont::DeviceAdapterId device, vtkm::cont::Token& token) { return ArrayT::StorageType::CreateWritePortal(array.GetBuffers(), device, token); } template typename std::decay::type::ReadPortalType GetWritePortalImpl( std::false_type, ArrayT&& array, vtkm::cont::DeviceAdapterId device, vtkm::cont::Token& token) { return ArrayT::StorageType::CreateReadPortal(array.GetBuffers(), device, token); } } // namespace detail // Get portal types: // We allow writing to an AHDecorator if *any* of the ArrayHandles are writable. // This means we have to avoid calling PrepareForOutput, etc on non-writable // array handles, since these may throw. On non-writable handles, use the // const array handles so we can at least read from them in the inverse // functors. template using GetWritePortalType = std::conditional_t< vtkm::internal::PortalSupportsSets::type::WritePortalType>::value, typename std::decay::type::WritePortalType, typename std::decay::type::ReadPortalType>; template using GetReadPortalType = typename std::decay::type::ReadPortalType; // Get portal objects: // See note above -- we swap in const portals sometimes. template GetWritePortalType::type> WritePortal(ArrayT&& array, vtkm::cont::DeviceAdapterId device, vtkm::cont::Token& token) { return detail::GetWritePortalImpl( IsWritableArrayHandle{}, std::forward(array), device, token); } template GetReadPortalType::type> ReadPortal(const ArrayT& array, vtkm::cont::DeviceAdapterId device, vtkm::cont::Token& token) { return ArrayT::StorageType::CreateReadPortal(array.GetBuffers(), device, token); } // Equivalent to std::true_type if *any* portal in PortalList can be written to. // If all are read-only, std::false_type is used instead. template using AnyPortalIsWritable = vtkm::ListAny; // Set to std::true_type if DecoratorImplT::CreateInverseFunctor can be called // with the supplied portals, or std::false_type otherwise. template using IsFunctorInvertible = typename detail::IsFunctorInvertibleImpl::type; // Set to std::true_type if DecoratorImplT::AllocateSourceArrays can be called // with the supplied arrays, or std::false_type otherwise. template using IsDecoratorAllocatable = typename detail::IsDecoratorAllocatableImpl::type; // std::true_type/std::false_type depending on whether the decorator impl has a // CreateInversePortal method AND any of the arrays are writable. template using CanWriteToFunctor = vtkm::internal::meta::And, AnyPortalIsWritable>; // The FunctorType for the provided implementation and portal types. template using GetFunctorType = typename detail::GetFunctorTypeImpl::type; // The InverseFunctorType for the provided implementation and portal types. // Will detect when inversion is not possible and return a NoOp functor instead. template using GetInverseFunctorType = typename detail::GetInverseFunctorTypeImpl, DecoratorImplT, PortalList>::type; // Convert a sequence of array handle types to a list of portals: // Some notes on this implementation: // - A more straightforward way to implement these to types would be to take // a simple template that takes a vtkm::List of array types and convert // that with vtkm::ListTransform. However, // this causes a strange compiler error in VS 2017 when evaluating the // `ValueType` in `DecoratorStorageTraits`. (It does not recognize the // decorator impl functor as a function taking one argument, even when it // effectively is.) A previous similar implementation caused an ICE in // VS 2015 (although that compiler is no longer supported anyway). // - The same problem happens with VS 2017 if you just try to create a list // with vtkm::List...>. // - So we jump through some decltype/declval hoops here to get this to work: template using GetReadPortalList = vtkm::List(), std::declval(), std::declval())))...>; template using GetWritePortalList = vtkm::List(), std::declval(), std::declval())))...>; template struct DecoratorMetaData { DecoratorImplT Implementation; vtkm::Id NumberOfValues = 0; std::array BufferOffsets; template DecoratorMetaData(const DecoratorImplT& implementation, vtkm::Id numValues, const ArrayTs... arrays) : Implementation(implementation) , NumberOfValues(numValues) { auto numBuffers = { std::size_t{ 1 }, arrays.GetBuffers().size()... }; std::partial_sum(numBuffers.begin(), numBuffers.end(), this->BufferOffsets.begin()); } DecoratorMetaData() = default; }; template struct DecoratorStorageTraits { using ArrayList = vtkm::List; VTKM_STATIC_ASSERT_MSG(sizeof...(ArrayTs) > 0, "Must specify at least one source array handle for " "ArrayHandleDecorator. Consider using " "ArrayHandleImplicit instead."); // Need to check this here, since this traits struct is used in the // ArrayHandleDecorator superclass definition before any other // static_asserts could be used. VTKM_STATIC_ASSERT_MSG(decor::AllAreArrayHandles::value, "Trailing template parameters for " "ArrayHandleDecorator must be a list of ArrayHandle " "types."); using ArrayTupleType = vtkm::Tuple; // size_t integral constants that index ArrayTs: using IndexList = vtkmstd::make_index_sequence; using MetaData = DecoratorMetaData; static MetaData& GetMetaData(const std::vector& buffers) { return buffers[0].GetMetaData(); } // Converts a buffers array to the ArrayHandle at the given index. template static vtkm::TupleElement BuffersToArray( const std::vector& buffers) { const MetaData& metaData = GetMetaData(buffers); std::vector subBuffers( buffers.begin() + metaData.BufferOffsets[I], buffers.begin() + metaData.BufferOffsets[I + 1]); return vtkm::TupleElement(std::move(subBuffers)); } // true_type/false_type depending on whether the decorator supports Allocate: using IsAllocatable = IsDecoratorAllocatable; // Portal lists: // NOTE we have to pass the parameter pack here instead of using ArrayList // with vtkm::ListTransform, since that's causing problems with VS 2017: using WritePortalList = GetWritePortalList; using ReadPortalList = GetReadPortalList; // Functors: using WriteFunctorType = GetFunctorType; using ReadFunctorType = GetFunctorType; // Inverse functors: using InverseWriteFunctorType = GetInverseFunctorType; using InverseReadFunctorType = vtkm::internal::decor::NoOpInverseFunctor; // Misc: // ValueType is derived from DecoratorImplT::CreateFunctor(...)'s operator(). using ValueType = decltype(std::declval()(0)); // Decorator portals: using WritePortalType = vtkm::internal::ArrayPortalDecorator; using ReadPortalType = vtkm::internal::ArrayPortalDecorator; // helper for constructing portals with the appropriate functors. This is // where we decide whether or not to call `CreateInverseFunctor` on the // implementation class. // Do not use these directly, they are helpers for the MakePortal[...] // methods below. template VTKM_CONT static typename std::enable_if::type CreatePortalDecorator(vtkm::Id numVals, const DecoratorImplT& impl, PortalTs&&... portals) { // Portal is read only: return { impl.CreateFunctor(std::forward(portals)...), typename DecoratorPortalType::InverseFunctorType{}, numVals }; } template VTKM_CONT static typename std::enable_if::type CreatePortalDecorator(vtkm::Id numVals, const DecoratorImplT& impl, PortalTs... portals) { // Portal is read/write: return { impl.CreateFunctor(portals...), impl.CreateInverseFunctor(portals...), numVals }; } // Static dispatch for calling AllocateSourceArrays on supported implementations: VTKM_CONT [[noreturn]] static void CallAllocate(std::false_type, vtkm::Id, const std::vector&, vtkm::CopyFlag, vtkm::cont::Token&, ArrayTs...) { throw vtkm::cont::ErrorBadType("Allocate not supported by this ArrayHandleDecorator."); } VTKM_CONT static void CallAllocate(std::true_type, vtkm::Id newSize, const std::vector& buffers, vtkm::CopyFlag preserve, vtkm::cont::Token& token, ArrayTs... arrays) { MetaData& metadata = GetMetaData(buffers); metadata.Implementation.AllocateSourceArrays(newSize, preserve, token, arrays...); metadata.NumberOfValues = newSize; } // Portal construction methods. These actually create portals. template VTKM_CONT static WritePortalType CreateWritePortal( const std::vector& buffers, vtkm::Id numValues, vtkmstd::index_sequence, vtkm::cont::DeviceAdapterId device, vtkm::cont::Token& token) { return CreatePortalDecorator( numValues, GetMetaData(buffers).Implementation, WritePortal(BuffersToArray(buffers), device, token)...); } template VTKM_CONT static ReadPortalType CreateReadPortal( const std::vector& buffers, vtkm::Id numValues, vtkmstd::index_sequence, vtkm::cont::DeviceAdapterId device, vtkm::cont::Token& token) { return CreatePortalDecorator( numValues, GetMetaData(buffers).Implementation, ReadPortal(BuffersToArray(buffers), device, token)...); } template VTKM_CONT static void AllocateSourceArrays( vtkm::Id numValues, const std::vector& buffers, vtkm::CopyFlag preserve, vtkm::cont::Token& token, vtkmstd::index_sequence) { CallAllocate( IsAllocatable{}, numValues, buffers, preserve, token, BuffersToArray(buffers)...); } }; } // end namespace decor template struct VTKM_ALWAYS_EXPORT StorageTagDecorator { }; template class Storage::ValueType, StorageTagDecorator> { using Traits = decor::DecoratorStorageTraits; using IndexList = typename Traits::IndexList; using MetaData = typename Traits::MetaData; public: using ReadPortalType = typename Traits::ReadPortalType; using WritePortalType = typename Traits::WritePortalType; private: VTKM_CONT static vtkm::IdComponent GetNumberOfComponentsFlatImpl(vtkm::VecTraitsTagSizeStatic) { return vtkm::VecFlat::NUM_COMPONENTS; } VTKM_CONT static vtkm::IdComponent GetNumberOfComponentsFlatImpl(vtkm::VecTraitsTagSizeVariable) { // Currently only support getting the number of components for statically sized types. return 0; } public: VTKM_CONT static vtkm::IdComponent GetNumberOfComponentsFlat( const std::vector&) { return GetNumberOfComponentsFlatImpl(typename vtkm::VecTraits::IsSizeStatic{}); } VTKM_CONT static vtkm::Id GetNumberOfValues( const std::vector& buffers) { return Traits::GetMetaData(buffers).NumberOfValues; } VTKM_CONT static void ResizeBuffers(vtkm::Id numValues, const std::vector& buffers, vtkm::CopyFlag preserve, vtkm::cont::Token& token) { if (numValues != GetNumberOfValues(buffers)) { Traits::AllocateSourceArrays(numValues, buffers, preserve, token, IndexList{}); } else { // Do nothing. We have this condition to allow allocating the same size when the // array cannot be resized. } } VTKM_CONT static ReadPortalType CreateReadPortal( const std::vector& buffers, vtkm::cont::DeviceAdapterId device, vtkm::cont::Token& token) { return Traits::CreateReadPortal( buffers, GetNumberOfValues(buffers), IndexList{}, device, token); } VTKM_CONT static WritePortalType CreateWritePortal( const std::vector& buffers, vtkm::cont::DeviceAdapterId device, vtkm::cont::Token& token) { return Traits::CreateWritePortal( buffers, GetNumberOfValues(buffers), IndexList{}, device, token); } VTKM_CONT static std::vector CreateBuffers(const DecoratorImplT& implementation, vtkm::Id numValues, const ArrayTs&... arrays) { return vtkm::cont::internal::CreateBuffers(MetaData(implementation, numValues, arrays...), arrays...); } VTKM_CONT static std::vector CreateBuffers() { return CreateBuffers(DecoratorImplT{}, 0, ArrayTs{}...); } }; template struct DecoratorHandleTraits { using StorageTraits = decor::DecoratorStorageTraits; using ValueType = typename StorageTraits::ValueType; using StorageTag = StorageTagDecorator; using Superclass = vtkm::cont::ArrayHandle; }; } // namespace internal /// \brief A fancy ArrayHandle that can be used to modify the results from one /// or more source ArrayHandle. /// /// ArrayHandleDecorator is given a `DecoratorImplT` class and a list of one or /// more source ArrayHandles. There are no restrictions on the size or type of /// the source ArrayHandles. /// /// The decorator implementation class is described below: /// /// ``` /// struct ExampleDecoratorImplementation /// { /// /// // Takes one portal for each source array handle (only two shown). /// // Returns a functor that defines: /// // /// // VTKM_EXEC_CONT ValueType operator()(vtkm::Id id) const; /// // /// // which takes an index and returns a value which should be produced by /// // the source arrays somehow. This ValueType will be the ValueType of the /// // ArrayHandleDecorator. /// // /// // Both SomeFunctor::operator() and CreateFunctor must be const. /// // /// template /// VTKM_CONT /// SomeFunctor CreateFunctor(Portal1Type portal1, Portal2Type portal2) const; /// /// // Takes one portal for each source array handle (only two shown). /// // Returns a functor that defines: /// // /// // VTKM_EXEC_CONT void operator()(vtkm::Id id, ValueType val) const; /// // /// // which takes an index and a value, which should be used to modify one /// // or more of the source arrays. /// // /// // CreateInverseFunctor is optional; if not provided, the /// // ArrayHandleDecorator will be read-only. In addition, if all of the /// // source ArrayHandles are read-only, the inverse functor will not be used /// // and the ArrayHandleDecorator will be read only. /// // /// // Both SomeInverseFunctor::operator() and CreateInverseFunctor must be /// // const. /// // /// template /// VTKM_CONT /// SomeInverseFunctor CreateInverseFunctor(Portal1Type portal1, /// Portal2Type portal2) const; /// /// // Given a set of ArrayHandles and a size, implement what should happen /// // to the source ArrayHandles when Allocate() is called on the decorator /// // handle. /// // /// // AllocateSourceArrays is optional; if not provided, the /// // ArrayHandleDecorator will throw if its Allocate method is called. If /// // an implementation is present and doesn't throw, the /// // ArrayHandleDecorator's internal state is updated to show `size` as the /// // number of values. /// template /// VTKM_CONT /// void AllocateSourceArrays(vtkm::Id size, /// vtkm::CopyFlag preserve, /// vtkm::cont::Token& token, /// Array1Type array1, /// Array2Type array2) const; /// /// }; /// ``` /// /// There are several example DecoratorImpl classes provided in the /// UnitTestArrayHandleDecorator test file. /// template class ArrayHandleDecorator : public internal::DecoratorHandleTraits::type, typename std::decay::type...>::Superclass { private: using Traits = internal::DecoratorHandleTraits::type, typename std::decay::type...>; public: VTKM_ARRAY_HANDLE_SUBCLASS(ArrayHandleDecorator, (ArrayHandleDecorator::type, typename std::decay::type...>), (typename Traits::Superclass)); VTKM_CONT ArrayHandleDecorator(vtkm::Id numValues, const typename std::decay::type& impl, const typename std::decay::type&... arrays) : Superclass{ StorageType::CreateBuffers(impl, numValues, arrays...) } { } }; /// Create an ArrayHandleDecorator with the specified number of values that /// uses the provided DecoratorImplT and source ArrayHandles. /// template VTKM_CONT ArrayHandleDecorator::type, typename std::decay::type...> make_ArrayHandleDecorator(vtkm::Id numValues, DecoratorImplT&& f, ArrayTs&&... arrays) { using AHList = vtkm::List::type...>; VTKM_STATIC_ASSERT_MSG(sizeof...(ArrayTs) > 0, "Must specify at least one source array handle for " "ArrayHandleDecorator. Consider using " "ArrayHandleImplicit instead."); VTKM_STATIC_ASSERT_MSG(internal::decor::AllAreArrayHandles::value, "Trailing template parameters for " "ArrayHandleDecorator must be a list of ArrayHandle " "types."); return { numValues, std::forward(f), std::forward(arrays)... }; } } } // namespace vtkm::cont #ifdef VTKM_USE_TAO_SEQ #undef VTKM_USE_TAO_SEQ #endif #endif //vtk_m_ArrayHandleDecorator_h