//============================================================================ // 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 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 = brigand::all>; 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 ::type::WritePortalType, typename PortalConst = typename std::decay::type::ReadPortalType> using GetWritePortalType = typename brigand::if_, Portal, PortalConst>::type; template using GetReadPortalType = typename std::decay::type::ReadPortalType; template ::type::template ExecutionTypes::Portal, typename PortalConst = typename std::decay::type::template ExecutionTypes::PortalConst> using GetPortalExecutionType = typename brigand::if_, Portal, PortalConst>::type; template using GetPortalConstExecutionType = typename std::decay::type::template ExecutionTypes::PortalConst; // 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 = typename brigand::any>::type; // 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 = typename brigand::and_, AnyPortalIsWritable>::type; // 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: // - MSVC 2015 ICEs when using brigand::transform to convert a brigand::list // of arrayhandles to portals. So instead we pass the ArrayTs. // - Just using brigand::list...> fails, as // apparently that is an improper parameter pack expansion // - So we jump through some decltype/declval hoops here to get this to work: template using GetReadPortalList = brigand::list(), std::declval(), std::declval())))...>; template using GetWritePortalList = brigand::list(), std::declval(), std::declval())))...>; template struct BufferIndexImpl { static constexpr vtkm::IdComponent Value() { return BufferIndexImpl::Value() + vtkm::TupleElement::GetNumberOfBuffers(); } }; template struct BufferIndexImpl<0, ArrayTupleType> { static constexpr vtkm::IdComponent Value() { // One buffer reserved for metadata. return 1; } }; template struct DecoratorMetaData { DecoratorImplT Implementation; vtkm::Id NumberOfValues = 0; DecoratorMetaData(const DecoratorImplT& implementation, vtkm::Id numValues) : Implementation(implementation) , NumberOfValues(numValues) { } DecoratorMetaData() = default; }; template struct DecoratorStorageTraits { using ArrayList = brigand::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; // Returns the index into the buffers array for the array at the given index. template static constexpr vtkm::IdComponent BufferIndex() { return BufferIndexImpl::Value(); } // Converts a buffers array to the ArrayHandle at the given index. template static vtkm::TupleElement BuffersToArray( const vtkm::cont::internal::Buffer* buffers) { return vtkm::TupleElement(buffers + BufferIndex()); } using MetaData = DecoratorMetaData; static MetaData& GetMetaData(const vtkm::cont::internal::Buffer* buffers) { return buffers[0].GetMetaData(); } // 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 brigand::transform, since that's causing MSVC 2015 to ice: 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, vtkm::cont::internal::Buffer*, 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, vtkm::cont::internal::Buffer* 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 vtkm::cont::internal::Buffer* 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 vtkm::cont::internal::Buffer* 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, vtkm::cont::internal::Buffer* 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; VTKM_CONT constexpr static vtkm::IdComponent GetNumberOfBuffers() { return Traits::template BufferIndex(sizeof...(ArrayTs))>(); } VTKM_CONT static vtkm::Id GetNumberOfValues(const vtkm::cont::internal::Buffer* buffers) { return Traits::GetMetaData(buffers).NumberOfValues; } VTKM_CONT static void ResizeBuffers(vtkm::Id numValues, vtkm::cont::internal::Buffer* 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 vtkm::cont::internal::Buffer* buffers, vtkm::cont::DeviceAdapterId device, vtkm::cont::Token& token) { return Traits::CreateReadPortal( buffers, GetNumberOfValues(buffers), IndexList{}, device, token); } VTKM_CONT static WritePortalType CreateWritePortal(vtkm::cont::internal::Buffer* 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...); } }; template struct DecoratorHandleTraits { using StorageTraits = decor::DecoratorStorageTraits; using ValueType = typename StorageTraits::ValueType; using StorageTag = StorageTagDecorator; using StorageType = vtkm::cont::internal::Storage; using Superclass = vtkm::cont::ArrayHandle; }; } // namespace internal template VTKM_ARRAY_HANDLE_NEW_STYLE( T, VTKM_PASS_COMMAS(internal::StorageTagDecorator)); /// \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...>; using StorageType = typename Traits::StorageType; 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 = brigand::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