Support using arrays with dynamic Vec-likes as output arrays

When you use an `ArrayHandle` as an output array in a worklet (for example,
as a `FieldOut`), the fetch operation does not read values from the array
during the `Load`. Instead, it just constructs a new object. This makes
sense as an output array is expected to have garbage in it anyway.

This is a problem for some special arrays that contain `Vec`-like objects
that are sized dynamically. For example, if you use an
`ArrayHandleGroupVecVariable`, each entry is a dynamically sized `Vec`. The
array is referenced by creating a special version of `Vec` that holds a
reference to the array portal and an index. Components are retrieved and
set by accessing the memory in the array portal. This allows us to have a
dynamically sized `Vec` in the execution environment without having to
allocate within the worklet.

The problem comes when we want to use one of these arrays with `Vec`-like
objects for an output. The typical fetch fails because you cannot construct
one of these `Vec`-like objects without an array portal to bind it to. In
these cases, we need the fetch to create the `Vec`-like object by reading
it from the array. Even though the data will be garbage, you get the
necessary buffer into the array (and nothing more).

Previously, the problem was fixed by creating partial specializations of
the `Fetch` for these `ArrayHandle`s. This worked OK as long as you were
using the array directly. However, the approach failed if the `ArrayHandle`
was wrapped in another `ArrayHandle` (for example, if an `ArrayHandleView`
was applied to an `ArrayHandleGroupVecVariable`).

To get around this problem and simplify things, the basic `Fetch` for
direct output arrays is changed to handle all cases where the values in the
`ArrayHandle` cannot be directly constructed. A compile-time check of the
array's value type is checked with `std::is_default_constructible`. If it
can be constructed, then the array is not accessed. If it cannot be
constructed, then it grabs a value out of the array.
This commit is contained in:
Kenneth Moreland 2023-01-13 15:23:30 -07:00
parent 679a61bb71
commit eda6dc39f2
9 changed files with 82 additions and 141 deletions

@ -0,0 +1,36 @@
# Support using arrays with dynamic Vec-likes as output arrays
When you use an `ArrayHandle` as an output array in a worklet (for example,
as a `FieldOut`), the fetch operation does not read values from the array
during the `Load`. Instead, it just constructs a new object. This makes
sense as an output array is expected to have garbage in it anyway.
This is a problem for some special arrays that contain `Vec`-like objects
that are sized dynamically. For example, if you use an
`ArrayHandleGroupVecVariable`, each entry is a dynamically sized `Vec`. The
array is referenced by creating a special version of `Vec` that holds a
reference to the array portal and an index. Components are retrieved and
set by accessing the memory in the array portal. This allows us to have a
dynamically sized `Vec` in the execution environment without having to
allocate within the worklet.
The problem comes when we want to use one of these arrays with `Vec`-like
objects for an output. The typical fetch fails because you cannot construct
one of these `Vec`-like objects without an array portal to bind it to. In
these cases, we need the fetch to create the `Vec`-like object by reading
it from the array. Even though the data will be garbage, you get the
necessary buffer into the array (and nothing more).
Previously, the problem was fixed by creating partial specializations of
the `Fetch` for these `ArrayHandle`s. This worked OK as long as you were
using the array directly. However, the approach failed if the `ArrayHandle`
was wrapped in another `ArrayHandle` (for example, if an `ArrayHandleView`
was applied to an `ArrayHandleGroupVecVariable`).
To get around this problem and simplify things, the basic `Fetch` for
direct output arrays is changed to handle all cases where the values in the
`ArrayHandle` cannot be directly constructed. A compile-time check of the
array's value type is checked with `std::is_default_constructible`. If it
can be constructed, then the array is not accessed. If it cannot be
constructed, then it grabs a value out of the array.

@ -31,14 +31,6 @@ class VecFromPortal
public:
using ComponentType = typename std::remove_const<typename PortalType::ValueType>::type;
VTKM_SUPPRESS_EXEC_WARNINGS
VTKM_EXEC_CONT
VecFromPortal()
: NumComponents(0)
, Offset(0)
{
}
VTKM_SUPPRESS_EXEC_WARNINGS
VTKM_EXEC_CONT
VecFromPortal(const PortalType& portal, vtkm::IdComponent numComponents = 0, vtkm::Id offset = 0)

@ -314,10 +314,6 @@ make_ArrayHandleGroupVecVariable(const ComponentsArrayHandleType& componentsArra
}
} // namespace vtkm::cont
//=============================================================================
// Specializations of worklet arguments using ArrayHandleGropuVecVariable
#include <vtkm/exec/arg/FetchTagArrayDirectOutArrayHandleGroupVecVariable.h>
//=============================================================================
// Specializations of serialization related classes
/// @cond SERIALIZATION

@ -78,7 +78,14 @@ public:
VTKM_EXEC_CONT RecombineVec& operator=(const RecombineVec& src)
{
this->DoCopy(src);
if ((&this->Portals[0] != &src.Portals[0]) || (this->Index != src.Index))
{
this->DoCopy(src);
}
else
{
// Copying to myself. Do not need to do anything.
}
return *this;
}
@ -648,8 +655,4 @@ struct ArrayExtractComponentImpl<vtkm::cont::internal::StorageTagRecombineVec>
}
} // namespace vtkm::cont
//=============================================================================
// Specializations of worklet arguments using ArrayHandleGropuVecVariable
#include <vtkm/exec/arg/FetchTagArrayDirectOutArrayHandleRecombineVec.h>
#endif //vtk_m_cont_ArrayHandleRecombineVec_h

@ -10,6 +10,7 @@
#include <vtkm/cont/ArrayHandleRecombineVec.h>
#include <vtkm/cont/ArrayHandleReverse.h>
#include <vtkm/cont/Invoker.h>
#include <vtkm/worklet/WorkletMapField.h>
@ -80,8 +81,12 @@ struct TestRecombineVecAsOutput
vtkm::cont::Invoker invoke;
invoke(PassThrough{}, baseArray, recombinedArray);
VTKM_TEST_ASSERT(test_equal_ArrayHandles(baseArray, outputArray));
// Try outputing to a recombine vec inside of another fancy ArrayHandle.
auto reverseOutput = vtkm::cont::make_ArrayHandleReverse(recombinedArray);
invoke(PassThrough{}, baseArray, reverseOutput);
VTKM_TEST_ASSERT(test_equal_ArrayHandles(baseArray, reverseOutput));
}
};

@ -19,8 +19,6 @@ set(headers
FetchTagArrayDirectIn.h
FetchTagArrayDirectInOut.h
FetchTagArrayDirectOut.h
FetchTagArrayDirectOutArrayHandleGroupVecVariable.h
FetchTagArrayDirectOutArrayHandleRecombineVec.h
FetchTagArrayNeighborhoodIn.h
FetchTagArrayTopologyMapIn.h
FetchTagExecObject.h

@ -13,6 +13,8 @@
#include <vtkm/exec/arg/AspectTagDefault.h>
#include <vtkm/exec/arg/Fetch.h>
#include <type_traits>
namespace vtkm
{
namespace exec
@ -35,14 +37,15 @@ struct Fetch<vtkm::exec::arg::FetchTagArrayDirectOut,
vtkm::exec::arg::AspectTagDefault,
ExecObjectType>
{
using ValueType = typename ExecObjectType::ValueType;
VTKM_SUPPRESS_EXEC_WARNINGS
template <typename ThreadIndicesType>
VTKM_EXEC auto Load(const ThreadIndicesType&, const ExecObjectType&) const ->
typename ExecObjectType::ValueType
VTKM_EXEC ValueType Load(const ThreadIndicesType& indices,
const ExecObjectType& arrayPortal) const
{
// Load is a no-op for this fetch.
using ValueType = typename ExecObjectType::ValueType;
return ValueType();
return this->DoLoad(
indices, arrayPortal, typename std::is_default_constructible<ValueType>::type{});
}
VTKM_SUPPRESS_EXEC_WARNINGS
@ -51,9 +54,32 @@ struct Fetch<vtkm::exec::arg::FetchTagArrayDirectOut,
const ExecObjectType& arrayPortal,
const T& value) const
{
using ValueType = typename ExecObjectType::ValueType;
arrayPortal.Set(indices.GetOutputIndex(), static_cast<ValueType>(value));
}
private:
VTKM_SUPPRESS_EXEC_WARNINGS
template <typename ThreadIndicesType>
VTKM_EXEC ValueType DoLoad(const ThreadIndicesType&, const ExecObjectType&, std::true_type) const
{
// Load is a no-op for this fetch.
return ValueType();
}
VTKM_SUPPRESS_EXEC_WARNINGS
template <typename ThreadIndicesType>
VTKM_EXEC ValueType DoLoad(const ThreadIndicesType& indices,
const ExecObjectType& arrayPortal,
std::false_type) const
{
// Cannot create a ValueType object, so pull one out of the array portal. This may seem
// weird because an output array often has garbage in it. However, this case can happen
// with special arrays with Vec-like values that reference back to the array memory.
// For example, with ArrayHandleRecombineVec, the values are actual objects that point
// back to the array for on demand reading and writing. You need the buffer established
// by the array even if there is garbage in that array.
return arrayPortal.Get(indices.GetOutputIndex());
}
};
}
}

@ -1,69 +0,0 @@
//============================================================================
// 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_exec_arg_FetchTagArrayDirectOutArrayHandleGroupVecVariable_h
#define vtk_m_exec_arg_FetchTagArrayDirectOutArrayHandleGroupVecVariable_h
#include <vtkm/exec/arg/FetchTagArrayDirectOut.h>
// We need to override the fetch for output fields using
// ArrayPortalGroupVecVariable because this portal does not behave like most
// ArrayPortals. Usually you ignore the Load and implement the Store. But if
// you ignore the Load, the VecFromPortal gets no portal to set values into.
// Instead, you need to implement the Load to point to the array portal. You
// can also ignore the Store because the data is already set in the array at
// that point.
// This file is included from ArrayHandleGroupVecVariable.h
namespace vtkm
{
namespace exec
{
namespace arg
{
// We need to override the fetch for output fields using
// ArrayPortalGroupVecVariable because this portal does not behave like most
// ArrayPortals. Usually you ignore the Load and implement the Store. But if
// you ignore the Load, the VecFromPortal gets no portal to set values into.
// Instead, you need to implement the Load to point to the array portal. You
// can also ignore the Store because the data is already set in the array at
// that point.
template <typename ComponentsPortalType, typename OffsetsPortalType>
struct Fetch<vtkm::exec::arg::FetchTagArrayDirectOut,
vtkm::exec::arg::AspectTagDefault,
vtkm::internal::ArrayPortalGroupVecVariable<ComponentsPortalType, OffsetsPortalType>>
{
using ExecObjectType =
vtkm::internal::ArrayPortalGroupVecVariable<ComponentsPortalType, OffsetsPortalType>;
using ValueType = typename ExecObjectType::ValueType;
VTKM_SUPPRESS_EXEC_WARNINGS
template <typename ThreadIndicesType>
VTKM_EXEC ValueType Load(const ThreadIndicesType& indices,
const ExecObjectType& arrayPortal) const
{
return arrayPortal.Get(indices.GetOutputIndex());
}
VTKM_SUPPRESS_EXEC_WARNINGS
template <typename ThreadIndicesType>
VTKM_EXEC void Store(const ThreadIndicesType&, const ExecObjectType&, const ValueType&) const
{
// We can actually ignore this because the VecFromPortal will already have
// set new values in the array.
}
};
}
}
} // namespace vtkm::exec::arg
#endif //vtk_m_exec_arg_FetchTagArrayDirectOutArrayHandleGroupVecVariable_h

@ -1,46 +0,0 @@
//============================================================================
// 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_exec_arg_FetchTagArrayDirectOutArrayHandleRecombineVec_h
#define vtk_m_exec_arg_FetchTagArrayDirectOutArrayHandleRecombineVec_h
#include <vtkm/exec/arg/FetchTagArrayDirectInOut.h>
#include <vtkm/exec/arg/FetchTagArrayDirectOut.h>
// The `Fetch` for direct array out breaks for `ArrayHandleRecombineVec` because the `Load`
// method attempts to create a `vtkm::internal::RecombineVec` with a default constructor,
// which does not exist. Instead, have the direct out `Fetch` behave like the direct in/out
// `Fetch`, which loads the initial value from the array. The actual load will not load the
// data but rather set up the portals in the returned object, which is necessary for the
// later `Store` to work anyway.
// This file is included from ArrayHandleRecombineVec.h
namespace vtkm
{
namespace exec
{
namespace arg
{
template <typename SourcePortalType>
struct Fetch<vtkm::exec::arg::FetchTagArrayDirectOut,
vtkm::exec::arg::AspectTagDefault,
vtkm::internal::ArrayPortalRecombineVec<SourcePortalType>>
: Fetch<vtkm::exec::arg::FetchTagArrayDirectInOut,
vtkm::exec::arg::AspectTagDefault,
vtkm::internal::ArrayPortalRecombineVec<SourcePortalType>>
{
};
}
}
} // namespace vtkm::exec::arg
#endif //vtk_m_exec_arg_FetchTagArrayDirectOutArrayHandleRecombineVec_h