From 8e570c8b640e639803a28a07001d8092a9981895 Mon Sep 17 00:00:00 2001 From: Kenneth Moreland Date: Sat, 11 May 2024 11:30:24 -0500 Subject: [PATCH] Add user guide chapter on memory layout in ArrayHandles --- docs/users-guide/basic-array-handles.rst | 8 +- docs/users-guide/examples/CMakeLists.txt | 2 + .../examples/GuideExampleArrayHandle.cxx | 61 +++ .../GuideExampleArrayHandleRuntimeVec.cxx | 139 +++++ .../GuideExampleUnknownArrayHandle.cxx | 515 ++++++++++++++++++ docs/users-guide/memory-layout.rst | 165 ++++++ docs/users-guide/part-advanced.rst | 1 + vtkm/cont/ArrayHandleBasic.h | 77 ++- vtkm/cont/ArrayHandleRuntimeVec.h | 22 +- vtkm/cont/ArrayHandleSOA.h | 289 ++++++++-- vtkm/cont/ArrayHandleStride.h | 34 ++ 11 files changed, 1256 insertions(+), 57 deletions(-) create mode 100644 docs/users-guide/examples/GuideExampleArrayHandleRuntimeVec.cxx create mode 100644 docs/users-guide/examples/GuideExampleUnknownArrayHandle.cxx create mode 100644 docs/users-guide/memory-layout.rst diff --git a/docs/users-guide/basic-array-handles.rst b/docs/users-guide/basic-array-handles.rst index daf2fe1b1..35f6638b6 100644 --- a/docs/users-guide/basic-array-handles.rst +++ b/docs/users-guide/basic-array-handles.rst @@ -2,11 +2,11 @@ Basic Array Handles ============================== -.. index:: array handle +.. index:: array handle; basic :chapref:`dataset:Data Sets` describes the basic data sets used by |VTKm|. This chapter dives deeper into how |VTKm| represents data. -Ultimately, data structures like \vtkmcont{DataSet} can be broken down into arrays of numbers. +Ultimately, data structures like :class:`vtkm::cont::DataSet` can be broken down into arrays of numbers. Arrays in |VTKm| are managed by a unit called an *array handle*. An array handle, which is implemented with the :class:`vtkm::cont::ArrayHandle` class, manages an array of data that can be accessed or manipulated by |VTKm| algorithms. @@ -43,6 +43,8 @@ This is convenient for creating arrays used as the output for algorithms. Chapter \ref{chap:AccessingAllocatingArrays} describes in detail how to allocate memory and access data in an :class:`vtkm::cont::ArrayHandle`. However, you can use the :func:`vtkm::cont::make_ArrayHandle` function for a simplified way to create an :class:`vtkm::cont::ArrayHandle` with data. +.. todo:: Update chapter reference above. Also consider moving the access/allocation chapter earlier. + :func:`vtkm::cont::make_ArrayHandle` has many forms. An easy form to use takes an initializer list and creates a basic :class:`vtkm::cont::ArrayHandle` with it. This allows you to create a short :class:`vtkm::cont::ArrayHandle` from literals. @@ -156,7 +158,7 @@ The Hidden Second Template Parameter double: array handle; storage We have already seen that :class:`vtkm::cont::ArrayHandle` is a templated class with the template parameter indicating the type of values stored in the array. -However, :class:`vtkm::cont::ArrayHandle` has a second hidden parameter that indicates the \keyterm{storage} of the array. +However, :class:`vtkm::cont::ArrayHandle` has a second hidden parameter that indicates the _storage_ of the array. We have so far been able to ignore this second template parameter because |VTKm| will assign a default storage for us that will store the data in a basic array. Changing the storage of an :class:`vtkm::cont::ArrayHandle` lets us do many weird and wonderful things. diff --git a/docs/users-guide/examples/CMakeLists.txt b/docs/users-guide/examples/CMakeLists.txt index 8e793fb97..bf7f714a9 100644 --- a/docs/users-guide/examples/CMakeLists.txt +++ b/docs/users-guide/examples/CMakeLists.txt @@ -10,6 +10,7 @@ set(examples GuideExampleArrayHandle.cxx + GuideExampleArrayHandleRuntimeVec.cxx GuideExampleCellShapes.cxx GuideExampleColorTables.cxx GuideExampleCoreDataTypes.cxx @@ -35,6 +36,7 @@ set(examples_device GuideExampleFilterDataSetWithField.cxx GuideExampleGenerateMeshConstantShape.cxx GuideExampleSimpleAlgorithm.cxx + GuideExampleUnknownArrayHandle.cxx GuideExampleUseWorkletMapField.cxx GuideExampleUseWorkletPointNeighborhood.cxx GuideExampleUseWorkletReduceByKey.cxx diff --git a/docs/users-guide/examples/GuideExampleArrayHandle.cxx b/docs/users-guide/examples/GuideExampleArrayHandle.cxx index 21982b2d5..6b6258ce5 100644 --- a/docs/users-guide/examples/GuideExampleArrayHandle.cxx +++ b/docs/users-guide/examples/GuideExampleArrayHandle.cxx @@ -12,6 +12,7 @@ #include #include +#include #include #include #include @@ -517,6 +518,64 @@ void TestExecutionPortalsExample() CheckArrayValues(outputArray, 2); } +//// +//// BEGIN-EXAMPLE GetArrayPointer +//// +void LegacyFunction(const int* data); + +void UseArrayWithLegacy(const vtkm::cont::ArrayHandle array) +{ + vtkm::cont::ArrayHandleBasic basicArray = array; + vtkm::cont::Token token; // Token prevents array from changing while in scope. + const int* cArray = basicArray.GetReadPointer(token); + LegacyFunction(cArray); + // When function returns, token goes out of scope and array can be modified. +} +//// +//// END-EXAMPLE GetArrayPointer +//// + +void LegacyFunction(const int* data) +{ + std::cout << "Got data: " << data[0] << std::endl; +} + +void TryUseArrayWithLegacy() +{ + vtkm::cont::ArrayHandle array; + array.Allocate(50); + SetPortal(array.WritePortal()); + UseArrayWithLegacy(array); +} + +void ArrayHandleFromComponents() +{ + //// + //// BEGIN-EXAMPLE ArrayHandleSOAFromComponentArrays + //// + vtkm::cont::ArrayHandle component1; + vtkm::cont::ArrayHandle component2; + vtkm::cont::ArrayHandle component3; + // Fill component arrays... + //// PAUSE-EXAMPLE + component1.AllocateAndFill(50, 0); + component2.AllocateAndFill(50, 1); + component3.AllocateAndFill(50, 2); + //// RESUME-EXAMPLE + + vtkm::cont::ArrayHandleSOA soaArray = + vtkm::cont::make_ArrayHandleSOA(component1, component2, component3); + //// + //// END-EXAMPLE ArrayHandleSOAFromComponentArrays + //// + + auto portal = soaArray.ReadPortal(); + for (vtkm::Id index = 0; index < portal.GetNumberOfValues(); ++index) + { + VTKM_TEST_ASSERT(portal.Get(index) == vtkm::Vec3f{ 0, 1, 2 }); + } +} + void Test() { BasicConstruction(); @@ -528,6 +587,8 @@ void Test() TestArrayPortalVectors(); TestControlPortalsExample(); TestExecutionPortalsExample(); + TryUseArrayWithLegacy(); + ArrayHandleFromComponents(); } } // anonymous namespace diff --git a/docs/users-guide/examples/GuideExampleArrayHandleRuntimeVec.cxx b/docs/users-guide/examples/GuideExampleArrayHandleRuntimeVec.cxx new file mode 100644 index 000000000..e8b5fafb0 --- /dev/null +++ b/docs/users-guide/examples/GuideExampleArrayHandleRuntimeVec.cxx @@ -0,0 +1,139 @@ +//============================================================================ +// 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 + +namespace +{ + +constexpr vtkm::Id ARRAY_SIZE = 10; + +void ReadArray(std::vector& data, int& numComponents) +{ + numComponents = 3; + data.resize(static_cast(ARRAY_SIZE * numComponents)); + std::fill(data.begin(), data.end(), 1.23f); +} + +//// +//// BEGIN-EXAMPLE GroupWithRuntimeVec +//// +void ReadArray(std::vector& data, int& numComponents); + +vtkm::cont::UnknownArrayHandle LoadData() +{ + // Read data from some external source where the vector size is determined at runtime. + std::vector data; + int numComponents; + ReadArray(data, numComponents); + + // Resulting ArrayHandleRuntimeVec gets wrapped in an UnknownArrayHandle + return vtkm::cont::make_ArrayHandleRuntimeVecMove( + static_cast(numComponents), std::move(data)); +} + +void UseVecArray(const vtkm::cont::UnknownArrayHandle& array) +{ + using ExpectedArrayType = vtkm::cont::ArrayHandle; + if (!array.CanConvert()) + { + throw vtkm::cont::ErrorBadType("Array unexpected type."); + } + + ExpectedArrayType concreteArray = array.AsArrayHandle(); + // Do something with concreteArray... + //// PAUSE-EXAMPLE + VTKM_TEST_ASSERT(concreteArray.GetNumberOfValues() == ARRAY_SIZE); + //// RESUME-EXAMPLE +} + +void LoadAndRun() +{ + // Load data in a routine that does not know component size until runtime. + vtkm::cont::UnknownArrayHandle array = LoadData(); + + // Use the data in a method that requires an array of static size. + // This will work as long as the `Vec` size matches correctly (3 in this case). + UseVecArray(array); +} +//// +//// END-EXAMPLE GroupWithRuntimeVec +//// + +template +void WriteData(const T*, std::size_t, int) +{ + // Dummy function for GetRuntimeVec. +} + +//// +//// BEGIN-EXAMPLE GetRuntimeVec +//// +template +void WriteData(const T* data, std::size_t size, int numComponents); + +void WriteVTKmArray(const vtkm::cont::UnknownArrayHandle& array) +{ + bool writeSuccess = false; + auto doWrite = [&](auto componentType) { + using ComponentType = decltype(componentType); + using VecArrayType = vtkm::cont::ArrayHandleRuntimeVec; + if (array.CanConvert()) + { + // Get the array as a runtime Vec. + VecArrayType runtimeVecArray = array.AsArrayHandle(); + + // Get the component array. + vtkm::cont::ArrayHandleBasic componentArray = + runtimeVecArray.GetComponentsArray(); + + // Use the general function to write the data. + WriteData(componentArray.GetReadPointer(), + componentArray.GetNumberOfValues(), + runtimeVecArray.GetNumberOfComponentsFlat()); + + writeSuccess = true; + } + }; + + // Figure out the base component type, retrieve the data (regardless + // of vec size), and write out the data. + vtkm::ListForEach(doWrite, vtkm::TypeListBaseC{}); +} +//// +//// END-EXAMPLE GetRuntimeVec +//// + +void DoWriteTest() +{ + vtkm::cont::ArrayHandle array; + array.Allocate(ARRAY_SIZE); + SetPortal(array.WritePortal()); + WriteVTKmArray(array); +} + +void Test() +{ + LoadAndRun(); + DoWriteTest(); +} + +} // anonymous namespace + +int GuideExampleArrayHandleRuntimeVec(int argc, char* argv[]) +{ + return vtkm::cont::testing::Testing::Run(Test, argc, argv); +} diff --git a/docs/users-guide/examples/GuideExampleUnknownArrayHandle.cxx b/docs/users-guide/examples/GuideExampleUnknownArrayHandle.cxx new file mode 100644 index 000000000..d49fa947a --- /dev/null +++ b/docs/users-guide/examples/GuideExampleUnknownArrayHandle.cxx @@ -0,0 +1,515 @@ +//============================================================================ +// 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 +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include + +namespace +{ + +constexpr vtkm::Id ARRAY_SIZE = 10; + +//// +//// BEGIN-EXAMPLE CreateUnknownArrayHandle +//// +VTKM_CONT +vtkm::cont::UnknownArrayHandle LoadUnknownArray(const void* buffer, + vtkm::Id length, + std::string type) +{ + vtkm::cont::UnknownArrayHandle handle; + if (type == "float") + { + vtkm::cont::ArrayHandle concreteArray = vtkm::cont::make_ArrayHandle( + reinterpret_cast(buffer), length, vtkm::CopyFlag::On); + handle = concreteArray; + } + else if (type == "int") + { + vtkm::cont::ArrayHandle concreteArray = vtkm::cont::make_ArrayHandle( + reinterpret_cast(buffer), length, vtkm::CopyFlag::On); + handle = concreteArray; + } + return handle; +} +//// +//// END-EXAMPLE CreateUnknownArrayHandle +//// + +void TryLoadUnknownArray() +{ + vtkm::Float32 scalarBuffer[ARRAY_SIZE]; + vtkm::cont::UnknownArrayHandle handle = + LoadUnknownArray(scalarBuffer, ARRAY_SIZE, "float"); + VTKM_TEST_ASSERT((handle.IsValueType()), "Type not right."); + VTKM_TEST_ASSERT(!(handle.IsValueType()), "Type not right."); + + vtkm::Int32 idBuffer[ARRAY_SIZE]; + handle = LoadUnknownArray(idBuffer, ARRAY_SIZE, "int"); + VTKM_TEST_ASSERT((handle.IsValueType()), "Type not right."); + VTKM_TEST_ASSERT(!(handle.IsValueType()), "Type not right."); +} + +void NonTypeUnknownArrayHandleAllocate() +{ + vtkm::cont::ArrayHandle concreteArray; + concreteArray.Allocate(ARRAY_SIZE); + //// + //// BEGIN-EXAMPLE NonTypeUnknownArrayHandleNewInstance + //// BEGIN-EXAMPLE UnknownArrayHandleResize + //// + vtkm::cont::UnknownArrayHandle unknownHandle = // ... some valid array + //// PAUSE-EXAMPLE + concreteArray; + //// RESUME-EXAMPLE + + // Double the size of the array while preserving all the initial values. + vtkm::Id originalArraySize = unknownHandle.GetNumberOfValues(); + unknownHandle.Allocate(originalArraySize * 2, vtkm::CopyFlag::On); + //// + //// END-EXAMPLE UnknownArrayHandleResize + //// + + // Create a new array of the same type as the original. + vtkm::cont::UnknownArrayHandle newArray = unknownHandle.NewInstance(); + + newArray.Allocate(originalArraySize); + //// + //// END-EXAMPLE NonTypeUnknownArrayHandleNewInstance + //// + + VTKM_TEST_ASSERT(originalArraySize == ARRAY_SIZE); + VTKM_TEST_ASSERT(unknownHandle.GetNumberOfValues() == (2 * ARRAY_SIZE)); + VTKM_TEST_ASSERT(concreteArray.GetNumberOfValues() == (2 * ARRAY_SIZE)); + VTKM_TEST_ASSERT(newArray.GetNumberOfValues() == ARRAY_SIZE); + VTKM_TEST_ASSERT(newArray.IsType()); + + //// + //// BEGIN-EXAMPLE UnknownArrayHandleBasicInstance + //// + vtkm::cont::UnknownArrayHandle indexArray = vtkm::cont::ArrayHandleIndex(); + // Returns an array of type ArrayHandleBasic + vtkm::cont::UnknownArrayHandle basicArray = indexArray.NewInstanceBasic(); + //// + //// END-EXAMPLE UnknownArrayHandleBasicInstance + //// + + VTKM_TEST_ASSERT(basicArray.IsType>()); + + //// + //// BEGIN-EXAMPLE UnknownArrayHandleFloatInstance + //// + vtkm::cont::UnknownArrayHandle intArray = vtkm::cont::ArrayHandleIndex(); + // Returns an array of type ArrayHandleBasic + vtkm::cont::UnknownArrayHandle floatArray = intArray.NewInstanceFloatBasic(); + + vtkm::cont::UnknownArrayHandle id3Array = vtkm::cont::ArrayHandle(); + // Returns an array of type ArrayHandleBasic + vtkm::cont::UnknownArrayHandle float3Array = id3Array.NewInstanceFloatBasic(); + //// + //// END-EXAMPLE UnknownArrayHandleFloatInstance + //// + + VTKM_TEST_ASSERT( + floatArray.IsType>()); + VTKM_TEST_ASSERT(float3Array.IsType>()); +} + +//// +//// BEGIN-EXAMPLE UnknownArrayHandleCanConvert +//// +VTKM_CONT vtkm::FloatDefault GetMiddleValue( + const vtkm::cont::UnknownArrayHandle& unknownArray) +{ + if (unknownArray.CanConvert>()) + { + // Fast path for known array + vtkm::cont::ArrayHandleConstant constantArray; + unknownArray.AsArrayHandle(constantArray); + return constantArray.GetValue(); + } + else + { + // General path + auto ranges = vtkm::cont::ArrayRangeCompute(unknownArray); + vtkm::Range range = ranges.ReadPortal().Get(0); + return static_cast((range.Min + range.Max) / 2); + } +} +//// +//// END-EXAMPLE UnknownArrayHandleCanConvert +//// + +//// +//// BEGIN-EXAMPLE UnknownArrayHandleDeepCopy +//// +VTKM_CONT vtkm::cont::ArrayHandle CopyToDefaultArray( + const vtkm::cont::UnknownArrayHandle& unknownArray) +{ + // Initialize the output UnknownArrayHandle with the array type we want to copy to. + vtkm::cont::UnknownArrayHandle output = vtkm::cont::ArrayHandle{}; + output.DeepCopyFrom(unknownArray); + return output.AsArrayHandle>(); +} +//// +//// END-EXAMPLE UnknownArrayHandleDeepCopy +//// + +//// +//// BEGIN-EXAMPLE UnknownArrayHandleShallowCopy +//// +VTKM_CONT vtkm::cont::ArrayHandle GetAsDefaultArray( + const vtkm::cont::UnknownArrayHandle& unknownArray) +{ + // Initialize the output UnknownArrayHandle with the array type we want to copy to. + vtkm::cont::UnknownArrayHandle output = vtkm::cont::ArrayHandle{}; + output.CopyShallowIfPossible(unknownArray); + return output.AsArrayHandle>(); +} +//// +//// END-EXAMPLE UnknownArrayHandleShallowCopy +//// + +void CastUnknownArrayHandle() +{ + //// + //// BEGIN-EXAMPLE UnknownArrayHandleAsCastArray + //// + vtkm::cont::ArrayHandle originalArray; + vtkm::cont::UnknownArrayHandle unknownArray = originalArray; + + vtkm::cont::ArrayHandleCast castArray; + unknownArray.AsArrayHandle(castArray); + //// + //// END-EXAMPLE UnknownArrayHandleAsCastArray + //// + + //// + //// BEGIN-EXAMPLE UnknownArrayHandleAsArrayHandle1 + //// + vtkm::cont::ArrayHandle knownArray = + unknownArray.AsArrayHandle>(); + //// + //// END-EXAMPLE UnknownArrayHandleAsArrayHandle1 + //// + + //// + //// BEGIN-EXAMPLE UnknownArrayHandleAsArrayHandle2 + //// + unknownArray.AsArrayHandle(knownArray); + //// + //// END-EXAMPLE UnknownArrayHandleAsArrayHandle2 + //// + + originalArray.Allocate(ARRAY_SIZE); + SetPortal(originalArray.WritePortal()); + + GetMiddleValue(unknownArray); + CopyToDefaultArray(unknownArray); + GetAsDefaultArray(unknownArray); +} + +//// +//// BEGIN-EXAMPLE UsingCastAndCallForTypes +//// +struct PrintArrayContentsFunctor +{ + template + VTKM_CONT void operator()(const vtkm::cont::ArrayHandle& array) const + { + this->PrintArrayPortal(array.ReadPortal()); + } + +private: + template + VTKM_CONT void PrintArrayPortal(const PortalType& portal) const + { + for (vtkm::Id index = 0; index < portal.GetNumberOfValues(); index++) + { + // All ArrayPortal objects have ValueType for the type of each value. + using ValueType = typename PortalType::ValueType; + using VTraits = vtkm::VecTraits; + + ValueType value = portal.Get(index); + + vtkm::IdComponent numComponents = VTraits::GetNumberOfComponents(value); + for (vtkm::IdComponent componentIndex = 0; componentIndex < numComponents; + componentIndex++) + { + std::cout << " " << VTraits::GetComponent(value, componentIndex); + } + std::cout << std::endl; + } + } +}; + +void PrintArrayContents(const vtkm::cont::UnknownArrayHandle& array) +{ + array.CastAndCallForTypes( + PrintArrayContentsFunctor{}); +} +//// +//// END-EXAMPLE UsingCastAndCallForTypes +//// + +struct MyWorklet : vtkm::worklet::WorkletMapField +{ + using ControlSignature = void(FieldIn, FieldOut); + + template + VTKM_EXEC void operator()(const T1& in, T2& out) const + { + using VTraitsIn = vtkm::VecTraits; + using VTraitsOut = vtkm::VecTraits; + const vtkm::IdComponent numComponents = VTraitsIn::GetNumberOfComponents(in); + VTKM_ASSERT(numComponents == VTraitsOut::GetNumberOfComponents(out)); + for (vtkm::IdComponent index = 0; index < numComponents; ++index) + { + VTraitsOut::SetComponent(out, + index, + static_cast( + VTraitsIn::GetComponent(in, index))); + } + } +}; + +void TryPrintArrayContents() +{ + vtkm::cont::ArrayHandleIndex implicitArray(ARRAY_SIZE); + + vtkm::cont::ArrayHandle concreteArray; + vtkm::cont::Algorithm::Copy(implicitArray, concreteArray); + + vtkm::cont::UnknownArrayHandle unknownArray = concreteArray; + + PrintArrayContents(unknownArray); + + //// + //// BEGIN-EXAMPLE UncertainArrayHandle + //// + vtkm::cont::UncertainArrayHandle + uncertainArray(unknownArray); + uncertainArray.CastAndCall(PrintArrayContentsFunctor{}); + //// + //// END-EXAMPLE UncertainArrayHandle + //// + + vtkm::cont::ArrayHandle outArray; + //// + //// BEGIN-EXAMPLE UnknownArrayResetTypes + //// + vtkm::cont::Invoker invoke; + invoke( + MyWorklet{}, + unknownArray.ResetTypes(), + outArray); + //// + //// END-EXAMPLE UnknownArrayResetTypes + //// + + //// + //// BEGIN-EXAMPLE CastAndCallForTypesWithFloatFallback + //// + unknownArray.CastAndCallForTypesWithFloatFallback( + PrintArrayContentsFunctor{}); + //// + //// END-EXAMPLE CastAndCallForTypesWithFloatFallback + //// + + //// + //// BEGIN-EXAMPLE CastAndCallWithFloatFallback + //// + uncertainArray.CastAndCall(PrintArrayContentsFunctor{}); + //// + //// END-EXAMPLE CastAndCallWithFloatFallback + //// +} + +void ExtractUnknownComponent() +{ + //// + //// BEGIN-EXAMPLE UnknownArrayExtractComponent + //// + vtkm::cont::ArrayHandleBasic concreteArray = + vtkm::cont::make_ArrayHandle({ { 0, 1, 2 }, + { 3, 4, 5 }, + { 6, 7, 8 }, + { 9, 10, 11 }, + { 12, 13, 14 }, + { 15, 16, 17 } }); + + vtkm::cont::UnknownArrayHandle unknownArray(concreteArray); + + //// LABEL Call + auto componentArray = unknownArray.ExtractComponent(0); + // componentArray contains [ 0, 3, 6, 9, 12, 15 ]. + //// + //// END-EXAMPLE UnknownArrayExtractComponent + //// + VTKM_TEST_ASSERT(componentArray.GetNumberOfValues() == + concreteArray.GetNumberOfValues()); + { + auto portal = componentArray.ReadPortal(); + auto expectedPortal = concreteArray.ReadPortal(); + for (vtkm::IdComponent i = 0; i < componentArray.GetNumberOfValues(); ++i) + { + VTKM_TEST_ASSERT(test_equal(portal.Get(i), expectedPortal.Get(i)[0])); + } + } + + VTKM_TEST_ASSERT( + //// + //// BEGIN-EXAMPLE UnknownArrayBaseComponentType + //// + unknownArray.IsBaseComponentType() + //// + //// END-EXAMPLE UnknownArrayBaseComponentType + //// + ); + + auto deepTypeArray = vtkm::cont::make_ArrayHandleGroupVec<2>(concreteArray); + + unknownArray = deepTypeArray; + VTKM_TEST_ASSERT(unknownArray.GetNumberOfComponentsFlat() == 6); + + vtkm::cont::ArrayHandle outputArray; + + vtkm::cont::Invoker invoke; + + //// + //// BEGIN-EXAMPLE UnknownArrayExtractComponentsMultiple + //// + std::vector> outputArrays( + static_cast(unknownArray.GetNumberOfComponentsFlat())); + for (vtkm::IdComponent componentIndex = 0; + componentIndex < unknownArray.GetNumberOfComponentsFlat(); + ++componentIndex) + { + invoke(MyWorklet{}, + unknownArray.ExtractComponent(componentIndex), + outputArrays[static_cast(componentIndex)]); + } + //// + //// END-EXAMPLE UnknownArrayExtractComponentsMultiple + //// + for (std::size_t outIndex = 0; outIndex < outputArrays.size(); ++outIndex) + { + vtkm::IdComponent vecIndex = static_cast(outIndex % 3); + vtkm::IdComponent groupIndex = static_cast(outIndex / 3); + auto portal = outputArrays[outIndex].ReadPortal(); + auto expectedPortal = deepTypeArray.ReadPortal(); + VTKM_TEST_ASSERT(portal.GetNumberOfValues() == + (concreteArray.GetNumberOfValues() / 2)); + for (vtkm::IdComponent i = 0; i < portal.GetNumberOfValues(); ++i) + { + VTKM_TEST_ASSERT( + test_equal(portal.Get(i), expectedPortal.Get(i)[groupIndex][vecIndex])); + } + } + + unknownArray = concreteArray; + + vtkm::cont::ArrayHandle outArray; + + //// + //// BEGIN-EXAMPLE UnknownArrayExtractArrayFromComponents + //// + invoke(MyWorklet{}, + unknownArray.ExtractArrayFromComponents(), + outArray); + //// + //// END-EXAMPLE UnknownArrayExtractArrayFromComponents + //// + VTKM_TEST_ASSERT(test_equal_ArrayHandles(outArray, concreteArray)); + + //// + //// BEGIN-EXAMPLE UnknownArrayCallWithExtractedArray + //// + unknownArray.CastAndCallWithExtractedArray(PrintArrayContentsFunctor{}); + //// + //// END-EXAMPLE UnknownArrayCallWithExtractedArray + //// +} + +//// +//// BEGIN-EXAMPLE UnknownArrayConstOutput +//// +void IndexInitialize(vtkm::Id size, const vtkm::cont::UnknownArrayHandle& output) +{ + vtkm::cont::ArrayHandleIndex input(size); + output.DeepCopyFrom(input); +} +//// +//// END-EXAMPLE UnknownArrayConstOutput +//// + +//// +//// BEGIN-EXAMPLE UseUnknownArrayConstOutput +//// +template +void Foo(const vtkm::cont::ArrayHandle& input, vtkm::cont::ArrayHandle& output) +{ + IndexInitialize(input.GetNumberOfValues(), output); + // ... + //// + //// END-EXAMPLE UseUnknownArrayConstOutput + //// + + VTKM_TEST_ASSERT(output.GetNumberOfValues() == input.GetNumberOfValues()); + auto portal = output.ReadPortal(); + for (vtkm::Id index = 0; index < portal.GetNumberOfValues(); ++index) + { + VTKM_TEST_ASSERT(portal.Get(index) == index); + } +} + +void TryConstOutput() +{ + vtkm::cont::ArrayHandle input = + vtkm::cont::make_ArrayHandle({ 3, 6, 1, 4 }); + vtkm::cont::ArrayHandle output; + Foo(input, output); +} + +void Test() +{ + TryLoadUnknownArray(); + NonTypeUnknownArrayHandleAllocate(); + CastUnknownArrayHandle(); + TryPrintArrayContents(); + ExtractUnknownComponent(); + TryConstOutput(); +} + +} // anonymous namespace + +int GuideExampleUnknownArrayHandle(int argc, char* argv[]) +{ + return vtkm::cont::testing::Testing::Run(Test, argc, argv); +} diff --git a/docs/users-guide/memory-layout.rst b/docs/users-guide/memory-layout.rst new file mode 100644 index 000000000..db5c94d21 --- /dev/null +++ b/docs/users-guide/memory-layout.rst @@ -0,0 +1,165 @@ +============================== +Memory Layout of Array Handles +============================== + +.. index:: array handle; memory layout + +:chapref:`basic-array-handles:Basic Array Handles` describes the basics of the :class:`vtkm::cont::ArrayHandle` class, which is the interface to the arrays of data that |VTKm| operates on. +Recall that :class:`vtkm::cont::ArrayHandle` is a templated class with two template parameters. +The first template argument is the type of each item in the array. +The second parameter, which is optional, determines how the array is stored in memory. +This can be used in a variety of different ways, but its primary purpose is to provide a strategy for laying the data out in memory. +This chapter documents the ways in which |VTKm| can store and access arrays of data in different layouts. + +------------------------------ +Basic Memory Layout +------------------------------ + +.. index:: + single: array handle; basic + single: basic array handle + +If the second storage template parameter of :class:`vtkm::cont::ArrayHandle` is not specified, it defaults to the basic memory layout. +This is roughly synonymous with a wrapper around a standard C array, much like ``std::vector``. +In fact, :secref:`basic-array-handles:Creating Array Handles` provides examples of wrapping a default :class:`vtkm::cont::ArrayHandle` around either a basic C array or a ``std::vector``. + +|VTKm| provides :class:`vtkm::cont::ArrayHandleBasic` as a convenience class for working with basic array handles. +:class:`vtkm::cont::ArrayHandleBasic` is a simple subclass of :class:`vtkm::cont::ArrayHandle` with the default storage in the second template argument (which is :class:`vtkm::cont::StorageTagBasic`). +:class:`vtkm::cont::ArrayHandleBasic` and its superclass can be used more or less interchangeably. + +.. doxygenclass:: vtkm::cont::ArrayHandleBasic + :members: + +Because a :class:`vtkm::cont::ArrayHandleBasic` represents arrays as a standard C array, it is possible to get a pointer to this array using either :func:`vtkm::cont::ArrayHandleBasic::GetReadPointer` or :func:`vtkm::cont::ArrayHandleBasic::GetWritePointer`. + +.. load-example:: GetArrayPointer + :file: GuideExampleArrayHandle.cxx + :caption: Getting a standard C array from a basic array handle. + +.. didyouknow:: + When you get an array pointer this way, the :class:`vtkm::cont::ArrayHandle` still has a reference to it. + If using multiple threads, you can use a :class:`vtkm::cont::Token` object to lock the array. + When the token is used to get a pointer, it will lock the array as long as the token exists. + :numref:`ex:GetArrayPointer` demonstrates using a :class:`vtkm::cont::Token`. + +-------------------- +Structure of Arrays +-------------------- + +.. index:: + single: AOS + single: SOA + +The basic :class:`vtkm::cont::ArrayHandle` stores :class:`vtkm::Vec` objects in sequence. +In this sense, a basic array is an *Array of Structures* (AOS). +Another approach is to store each component of the structure (i.e., the :class:`vtkm::Vec`) in a separate array. +This is known as a *Structure of Arrays* (SOA). +There are advantages to this approach including potentially better cache performance and the ability to combine arrays already represented as separate components without copying them. +Arrays of this nature are represented with a :class:`vtkm::cont::ArrayHandleSOA`, which is a subclass of :class:`vtkm::cont::StorageTagSOA`. + +.. doxygenclass:: vtkm::cont::ArrayHandleSOA + :members: + +:class:`vtkm::cont::ArrayHandleSOA` can be constructed and allocated just as a basic array handle. +Additionally, you can use its constructors or the :func:`vtkm::cont::make_ArrayHandleSOA` functions to build a :class:`vtkm::cont::ArrayHandleSOA` from basic :class:`vtkm::cont::ArrayHandle`'s that hold the components. + +.. doxygenfunction:: vtkm::cont::make_ArrayHandleSOA(std::initializer_list::ComponentType, vtkm::cont::StorageTagBasic>> &&) +.. doxygenfunction:: vtkm::cont::make_ArrayHandleSOA(const vtkm::cont::ArrayHandle&, const RemainingArrays&...) +.. doxygenfunction:: vtkm::cont::make_ArrayHandleSOA(std::initializer_list::ComponentType>>&&) +.. doxygenfunction:: vtkm::cont::make_ArrayHandleSOA(vtkm::CopyFlag, const std::vector&, RemainingVectors&&...) +.. doxygenfunction:: vtkm::cont::make_ArrayHandleSOA(vtkm::CopyFlag, std::vector&&, RemainingVectors&&...) +.. doxygenfunction:: vtkm::cont::make_ArrayHandleSOAMove(std::vector&&, RemainingVectors&&...) +.. doxygenfunction:: vtkm::cont::make_ArrayHandleSOA(std::initializer_list::ComponentType*>&&, vtkm::Id, vtkm::CopyFlag) +.. doxygenfunction:: vtkm::cont::make_ArrayHandleSOA(vtkm::Id, vtkm::CopyFlag, const ComponentType*, const RemainingArrays*...) + +.. load-example:: ArrayHandleSOAFromComponentArrays + :file: GuideExampleArrayHandle.cxx + :caption: Creating an SOA array handle from component arrays. + +.. didyouknow:: + In addition to constructing a :class:`vtkm::cont::ArrayHandleSOA` from its component arrays, you can get the component arrays back out using the :func:`vtkm::cont::ArrayHandleSOA::GetArray` method. + +-------------------- +Strided Arrays +-------------------- + +.. todo:: Should this be moved to the chapter/section on transformed arrays? + +.. index:: + double: array handle; stride + double: array handle; offset + double: array handle; modulo + double: array handle; divisor + +:class:`vtkm::cont::ArrayHandleBasic` operates on a tightly packed array. +That is, each value follows immediately after the proceeding value in memory. +However, it is often convenient to access values at different strides or offsets. +This allows representations of data that are not tightly packed in memory. +The :class:`vtkm::cont::ArrayHandleStride` class allows arrays with different data packing. + +.. doxygenclass:: vtkm::cont::ArrayHandleStride + :members: + +The most common use of :class:`vtkm::cont::ArrayHandleStride` is to pull components out of arrays. +:class:`vtkm::cont::ArrayHandleStride` is seldom constructed directly. +Rather, |VTKm| has mechanisms to extract a component from an array. +To extract a component directly from a :class:`vtkm::cont::ArrayHandle`, use :func:`vtkm::cont::ArrayExtractComponent`. + +.. doxygenfunction:: vtkm::cont::ArrayExtractComponent + +The main advantage of extracting components this way is to convert data represented in different types of arrays into an array of a single type. +For example, :class:`vtkm::cont::ArrayHandleStride` can represent a component from either a :class:`vtkm::cont::ArrayHandleBasic` or a :class:`vtkm::cont::ArrayHandleSOA` by just using different stride values. +This is used by :func:`vtkm::cont::UnknownArrayHandle::ExtractComponent` and elsewhere to create a concrete array handle class without knowing the actual class. + +.. commonerrors:: + Many, but not all, of |VTKm|'s arrays can be represented by a :class:`vtkm::cont::ArrayHandleStride` directly without copying. + If |VTKm| cannot easily create a :class:`vtkm::cont::ArrayHandleStride` when attempting such an operation, it will use a slow copying fallback. + A warning will be issued whenever this happens. + Be on the lookout for such warnings and consider changing the data representation when that happens. + +-------------------- +Runtime Vec Arrays +-------------------- + +Because many of the devices |VTKm| runs on cannot efficiently allocate memory while an algorithm is running, the data held in :class:`vtkm::cont::ArrayHandle`'s are usually required to be a static size. +For example, the :class:`vtkm::Vec` object often used as the value type for :class:`vtkm::cont::ArrayHandle` has a number of components that must be defined at compile time. + +This is a problem in cases where the size of a vector object cannot be determined at compile time. +One class to help alleviate this problem is :class:`vtkm::cont::ArrayHandleRuntimeVec`. +This array handle stores data in the same way as :class:`vtkm::cont::ArrayHandleBasic` with a :class:`vtkm::Vec` value type, but the size of the ``Vec`` can be set at runtime. + +.. doxygenclass:: vtkm::cont::ArrayHandleRuntimeVec + :members: + +A :class:`vtkm::cont::ArrayHandleRuntimeVec` is easily created from existing data using one of the :func:`vtkm::cont::make_ArrayHandleRuntimeVec` functions. + +.. doxygenfunction:: vtkm::cont::make_ArrayHandleRuntimeVec(vtkm::IdComponent, const vtkm::cont::ArrayHandle&) +.. doxygenfunction:: vtkm::cont::make_ArrayHandleRuntimeVec(const vtkm::cont::ArrayHandle&) + +|VTKm| also provides several convenience functions to convert a basic C array or ``std::vector`` to a :class:`vtkm::cont::ArrayHandleRuntimeVec`. + +.. doxygenfunction:: vtkm::cont::make_ArrayHandleRuntimeVec(vtkm::IdComponent, const T*, vtkm::Id, vtkm::CopyFlag) +.. doxygenfunction:: vtkm::cont::make_ArrayHandleRuntimeVecMove(vtkm::IdComponent, T*&, vtkm::Id, vtkm::cont::internal::BufferInfo::Deleter, vtkm::cont::internal::BufferInfo::Reallocater) +.. doxygenfunction:: vtkm::cont::make_ArrayHandleRuntimeVec(vtkm::IdComponent, const std::vector&, vtkm::CopyFlag) +.. doxygenfunction:: vtkm::cont::make_ArrayHandleRuntimeVecMove(vtkm::IdComponent, std::vector&&) + +The advantage of this class is that a :class:`vtkm::cont::ArrayHandleRuntimeVec` can be created in a routine that does not know the number of components at runtime and then later retrieved as a basic :class:`vtkm::cont::ArrayHandle` with a :class:`vtkm::Vec` of the correct size. +This often consists of a file reader or other data ingestion creating :class:`vtkm::cont::ArrayHandleRuntimeVec` objects and storing them in :class:`vtkm::cont::UnknownArrayHandle`, which is used as an array container for :class:`vtkm::cont::DataSet`. +Filters that then subsequently operate on the :class:`vtkm::cont::DataSet` can retrieve the data as a :class:`vtkm::cont::ArrayHandle` of the appropriate :class:`vtkm::Vec` size. + +.. load-example:: GroupWithRuntimeVec + :file: GuideExampleArrayHandleRuntimeVec.cxx + :caption: Loading a data with runtime component size and using with a static sized filter. + +.. didyouknow:: + Wrapping a basic array in a :class:`vtkm::cont::ArrayHandleRuntimeVec` has a similar effect as wrapping the array in a :class:`vtkm::cont::ArrayHandleGroupVec`. + The difference is in the context in which they are used. + If the size of the ``Vec`` is known at compile time *and* the array is going to immediately be used (such as operated on by a worklet), then :class:`vtkm::cont::ArrayHandleGroupVec` should be used. + However, if the ``Vec`` size is not known or the array will be stored in an object like :class:`vtkm::cont::UnknownArrayHandle`, then :class:`vtkm::cont::ArrayHandleRuntimeVec` is a better choice. + +It is also possible to get a :class:`vtkm::cont::ArrayHandleRuntimeVec` from a :class:`vtkm::cont::UnknownArrayHandle` that was originally stored as a basic array. +This is convenient for operations that want to operate on arrays with an unknown ``Vec`` size. + +.. load-example:: GetRuntimeVec + :file: GuideExampleArrayHandleRuntimeVec.cxx + :caption: Using :class:`vtkm::cont::ArrayHandleRuntimeVec` to get an array regardless of the size of the contained :class:`vtkm::Vec` values. diff --git a/docs/users-guide/part-advanced.rst b/docs/users-guide/part-advanced.rst index 4355565cd..f1f34fbb0 100644 --- a/docs/users-guide/part-advanced.rst +++ b/docs/users-guide/part-advanced.rst @@ -12,3 +12,4 @@ Advanced Development worklet-error-handling.rst math.rst working-with-cells.rst + memory-layout.rst diff --git a/vtkm/cont/ArrayHandleBasic.h b/vtkm/cont/ArrayHandleBasic.h index 5ada4f926..29db460e7 100644 --- a/vtkm/cont/ArrayHandleBasic.h +++ b/vtkm/cont/ArrayHandleBasic.h @@ -101,6 +101,12 @@ public: } // namespace internal +/// @brief Basic array storage for an array handle. +/// +/// This array handle references a standard C array. It provides a level +/// of safety and management across devices. +/// This is the default used when no storage is specified. Using this subclass +/// allows access to the underlying raw array. template class VTKM_ALWAYS_EXPORT ArrayHandleBasic : public ArrayHandle { @@ -173,50 +179,89 @@ public: { } - /// @{ - /// \brief Gets raw access to the `ArrayHandle`'s data. + /// @brief Gets raw access to the `ArrayHandle`'s data. /// - /// Note that the returned array may become invalidated by other operations on the ArryHandle - /// unless you provide a token. + /// Note that the returned array may become invalidated by other operations on the ArryHandle. /// - const T* GetReadPointer(vtkm::cont::Token& token) const - { - return reinterpret_cast(this->GetBuffers()[0].ReadPointerHost(token)); - } const T* GetReadPointer() const { vtkm::cont::Token token; return this->GetReadPointer(token); } - T* GetWritePointer(vtkm::cont::Token& token) const + /// @brief Gets raw access to the `ArrayHandle`'s data. + /// + /// @param token When a `vtkm::cont::Token` is provided, the array is locked + /// from being used by any write operations until the token goes out of scope. + /// + const T* GetReadPointer(vtkm::cont::Token& token) const { - return reinterpret_cast(this->GetBuffers()[0].WritePointerHost(token)); + return reinterpret_cast(this->GetBuffers()[0].ReadPointerHost(token)); } + /// @brief Gets raw write access to the `ArrayHandle`'s data. + /// + /// Note that the returned array may become invalidated by other operations on the ArryHandle. + /// T* GetWritePointer() const { vtkm::cont::Token token; return this->GetWritePointer(token); } - - const T* GetReadPointer(vtkm::cont::DeviceAdapterId device, vtkm::cont::Token& token) const + /// @brief Gets raw write access to the `ArrayHandle`'s data. + /// + /// @param token When a `vtkm::cont::Token` is provided, the array is locked + /// from being used by any read or write operations until the token goes out of scope. + /// + T* GetWritePointer(vtkm::cont::Token& token) const { - return reinterpret_cast(this->GetBuffers()[0].ReadPointerDevice(device, token)); + return reinterpret_cast(this->GetBuffers()[0].WritePointerHost(token)); } + + /// @brief Gets raw access to the `ArrayHandle`'s data on a particular device. + /// + /// Note that the returned array may become invalidated by other operations on the ArryHandle. + /// + /// @param device The device ID or device tag specifying on which device the array will + /// be valid on. + /// const T* GetReadPointer(vtkm::cont::DeviceAdapterId device) const { vtkm::cont::Token token; return this->GetReadPointer(device, token); } - T* GetWritePointer(vtkm::cont::DeviceAdapterId device, vtkm::cont::Token& token) const + /// @brief Gets raw access to the `ArrayHandle`'s data. + /// + /// @param device The device ID or device tag specifying on which device the array will + /// be valid on. + /// @param token When a `vtkm::cont::Token` is provided, the array is locked + /// from being used by any write operations until the token goes out of scope. + /// + const T* GetReadPointer(vtkm::cont::DeviceAdapterId device, vtkm::cont::Token& token) const { - return reinterpret_cast(this->GetBuffers()[0].WritePointerDevice(device, token)); + return reinterpret_cast(this->GetBuffers()[0].ReadPointerDevice(device, token)); } + /// @brief Gets raw write access to the `ArrayHandle`'s data. + /// + /// Note that the returned array may become invalidated by other operations on the ArryHandle. + /// + /// @param device The device ID or device tag specifying on which device the array will + /// be valid on. + /// T* GetWritePointer(vtkm::cont::DeviceAdapterId device) const { vtkm::cont::Token token; return this->GetWritePointer(device, token); } - /// @} + /// @brief Gets raw write access to the `ArrayHandle`'s data. + /// + /// @param device The device ID or device tag specifying on which device the array will + /// be valid on. + /// @param token When a `vtkm::cont::Token` is provided, the array is locked + /// from being used by any read or write operations until the token goes out of scope. + /// + T* GetWritePointer(vtkm::cont::DeviceAdapterId device, vtkm::cont::Token& token) const + { + return reinterpret_cast(this->GetBuffers()[0].WritePointerDevice(device, token)); + } }; /// A convenience function for creating an ArrayHandle from a standard C array. diff --git a/vtkm/cont/ArrayHandleRuntimeVec.h b/vtkm/cont/ArrayHandleRuntimeVec.h index ec40d3620..c23a2f865 100644 --- a/vtkm/cont/ArrayHandleRuntimeVec.h +++ b/vtkm/cont/ArrayHandleRuntimeVec.h @@ -296,7 +296,7 @@ public: } // namespace internal -/// \brief Fancy array handle for a basic array with runtime selected vec size. +/// @brief Fancy array handle for a basic array with runtime selected vec size. /// /// It is sometimes the case that you need to create an array of `Vec`s where /// the number of components is not known until runtime. This is problematic @@ -341,6 +341,14 @@ private: using ComponentsArrayType = vtkm::cont::ArrayHandle; public: + /// @brief Construct an `ArrayHandleRuntimeVec` with a given number of components. + /// + /// @param numComponents The size of the `Vec`s stored in the array. This must be + /// specified at the time of construction. + /// + /// @param componentsArray This optional parameter allows you to supply a basic array + /// that holds the components. This provides a mechanism to group consecutive values + /// into vectors. VTKM_CONT ArrayHandleRuntimeVec(vtkm::IdComponent numComponents, const ComponentsArrayType& componentsArray = ComponentsArrayType{}) @@ -348,18 +356,22 @@ public: { } + /// @brief Return the number of components in each vec value. VTKM_CONT vtkm::IdComponent GetNumberOfComponents() const { return StorageType::GetNumberOfComponents(this->GetBuffers()); } + /// @brief Return a basic array containing the components stored in this array. + /// + /// The returned array is shared with this object. Modifying the contents of one array + /// will modify the other. VTKM_CONT vtkm::cont::ArrayHandleBasic GetComponentsArray() const { return StorageType::GetComponentsArray(this->GetBuffers()); } - ///@{ - /// \brief Converts the array to that of a basic array handle. + /// @brief Converts the array to that of a basic array handle. /// /// This method converts the `ArrayHandleRuntimeVec` to a simple `ArrayHandleBasic`. /// This is useful if the `ArrayHandleRuntimeVec` is passed to a routine that works @@ -371,6 +383,7 @@ public: StorageType::AsArrayHandleBasic(this->GetBuffers(), array); } + /// @copydoc AsArrayHandleBasic template ArrayType AsArrayHandleBasic() const { @@ -378,7 +391,6 @@ public: this->AsArrayHandleBasic(array); return array; } - ///@} }; /// `make_ArrayHandleRuntimeVec` is convenience function to generate an @@ -407,6 +419,8 @@ VTKM_CONT auto make_ArrayHandleRuntimeVec( numComponents * UnrolledVec::NUM_COMPONENTS, flatComponents); } +/// Converts a basic array handle into an `ArrayHandleRuntimeVec` with 1 component. The +/// constructed array is essentially equivalent but of a different type. template VTKM_CONT auto make_ArrayHandleRuntimeVec( const vtkm::cont::ArrayHandle& componentsArray) diff --git a/vtkm/cont/ArrayHandleSOA.h b/vtkm/cont/ArrayHandleSOA.h index b4c3a8f15..5fda6e13f 100644 --- a/vtkm/cont/ArrayHandleSOA.h +++ b/vtkm/cont/ArrayHandleSOA.h @@ -229,7 +229,7 @@ public: } // namespace internal -/// \brief An `ArrayHandle` that for Vecs stores each component in a separate physical array. +/// @brief An `ArrayHandle` that for Vecs stores each component in a separate physical array. /// /// `ArrayHandleSOA` behaves like a regular `ArrayHandle` (with a basic storage) except that /// if you specify a `ValueType` of a `Vec` or a `Vec-like`, it will actually store each @@ -264,6 +264,17 @@ public: { } + /// @brief Construct an `ArrayHandleSOA` from a collection of component arrays. + /// + /// @code{.cpp} + /// vtkm::cont::ArrayHandle components1; + /// vtkm::cont::ArrayHandle components2; + /// vtkm::cont::ArrayHandle components3; + /// // Fill arrays... + /// + /// std::array allComponents{ components1, components2, components3 }; + /// vtkm::cont::make_ArrayHandleSOAvecarray(allComponents); + /// @endcode ArrayHandleSOA(const std::array& componentArrays) { for (vtkm::IdComponent componentIndex = 0; componentIndex < NUM_COMPONENTS; ++componentIndex) @@ -272,6 +283,17 @@ public: } } + /// @brief Construct an `ArrayHandleSOA` from a collection of component arrays. + /// + /// @code{.cpp} + /// vtkm::cont::ArrayHandle components1; + /// vtkm::cont::ArrayHandle components2; + /// vtkm::cont::ArrayHandle components3; + /// // Fill arrays... + /// + /// std::vector allComponents{ components1, components2, components3 }; + /// vtkm::cont::make_ArrayHandleSOAvecarray(allComponents); + /// @endcode ArrayHandleSOA(const std::vector& componentArrays) { VTKM_ASSERT(componentArrays.size() == NUM_COMPONENTS); @@ -281,6 +303,17 @@ public: } } + /// @brief Construct an `ArrayHandleSOA` from a collection of component arrays. + /// + /// @code{.cpp} + /// vtkm::cont::ArrayHandle components1; + /// vtkm::cont::ArrayHandle components2; + /// vtkm::cont::ArrayHandle components3; + /// // Fill arrays... + /// + /// vtkm::cont::make_ArrayHandleSOA vecarray( + /// { components1, components2, components3 }); + /// @endcode ArrayHandleSOA(std::initializer_list&& componentArrays) { VTKM_ASSERT(componentArrays.size() == NUM_COMPONENTS); @@ -292,6 +325,19 @@ public: } } + /// @brief Construct an `ArrayHandleSOA` from a collection of component arrays. + /// + /// The data is copied from the `std::vector`s to the array handle. + /// + /// @code{.cpp} + /// std::vector components1; + /// std::vector components2; + /// std::vector components3; + /// // Fill arrays... + /// + /// vtkm::cont::ArrayHandleSOA> vecarray( + /// { components1, components2, components3 }); + /// @endcode ArrayHandleSOA(std::initializer_list>&& componentVectors) { VTKM_ASSERT(componentVectors.size() == NUM_COMPONENTS); @@ -305,7 +351,22 @@ public: } } - // This only works if all the templated arguments are of type std::vector. + /// @brief Construct an `ArrayHandleSOA` from a collection of component arrays. + /// + /// The first argument is a `vtkm::CopyFlag` to determine whether the input arrays + /// should be copied. + /// The component arrays are listed as arguments. + /// This only works if all the templated arguments are of type `std::vector`. + /// + /// @code{.cpp} + /// std::vector components1; + /// std::vector components2; + /// std::vector components3; + /// // Fill arrays... + /// + /// vtkm::cont::ArrayHandleSOA> vecarray( + /// vtkm::CopyFlag::On, components1, components2, components3); + /// @endcode template ArrayHandleSOA(vtkm::CopyFlag copy, const std::vector& vector0, @@ -318,7 +379,25 @@ public: VTKM_STATIC_ASSERT(sizeof...(RemainingVectors) + 1 == NUM_COMPONENTS); } - // This only works if all the templated arguments are of type std::vector. + /// @brief Construct an `ArrayHandleSOA` from a collection of component arrays. + /// + /// The first argument is a `vtkm::CopyFlag` to determine whether the input arrays + /// should be copied. + /// The component arrays are listed as arguments. + /// This only works if all the templated arguments are rvalues of type + /// `std::vector`. + /// + /// @code{.cpp} + /// std::vector components1; + /// std::vector components2; + /// std::vector components3; + /// // Fill arrays... + /// + /// vtkm::cont::ArrayHandleSOA vecarray(vtkm::CopyFlag::Off, + /// std::move(components1), + /// std::move(components2), + /// std::move(components3); + /// @endcode template ArrayHandleSOA(vtkm::CopyFlag copy, std::vector&& vector0, @@ -331,6 +410,17 @@ public: VTKM_STATIC_ASSERT(sizeof...(RemainingVectors) + 1 == NUM_COMPONENTS); } + /// @brief Construct an `ArrayHandleSOA` from a collection of component arrays. + /// + /// @code{.cpp} + /// T* components1; + /// T* components2; + /// T* components3; + /// // Fill arrays... + /// + /// vtkm::cont::ArrayHandleSOA>( + /// { components1, components2, components3 }, size, vtkm::CopyFlag::On); + /// @endcode ArrayHandleSOA(std::initializer_list componentArrays, vtkm::Id length, vtkm::CopyFlag copy) @@ -345,7 +435,20 @@ public: } } - // This only works if all the templated arguments are of type std::vector. + /// @brief Construct an `ArrayHandleSOA` from a collection of component arrays. + /// + /// The component arrays are listed as arguments. + /// This only works if all the templated arguments are of type `ComponentType*`. + /// + /// @code{.cpp} + /// T* components1; + /// T* components2; + /// T* components3; + /// // Fill arrays... + /// + /// vtkm::cont::ArrayHandleSOA> vecarray( + /// size, vtkm::CopyFlag::On, components1, components2, components3); + /// @endcode template ArrayHandleSOA(vtkm::Id length, vtkm::CopyFlag copy, @@ -358,17 +461,39 @@ public: VTKM_STATIC_ASSERT(sizeof...(RemainingArrays) + 1 == NUM_COMPONENTS); } + /// @brief Get a basic array representing the component for the given index. VTKM_CONT vtkm::cont::ArrayHandleBasic GetArray(vtkm::IdComponent index) const { return ComponentArrayType({ this->GetBuffers()[index] }); } + /// @brief Replace a component array. VTKM_CONT void SetArray(vtkm::IdComponent index, const ComponentArrayType& array) { this->SetBuffer(index, array.GetBuffers()[0]); } }; +namespace internal +{ + +template +using VecSizeFromRemaining = + std::integral_constant; + +} // namespace internal + +/// @brief Create a `vtkm::cont::ArrayHandleSOA` with an initializer list of array handles. +/// +/// @code{.cpp} +/// vtkm::cont::ArrayHandle components1; +/// vtkm::cont::ArrayHandle components2; +/// vtkm::cont::ArrayHandle components3; +/// // Fill arrays... +/// +/// auto vecarray = vtkm::cont::make_ArrayHandleSOA>( +/// { components1, components2, components3 }); +/// @endcode template VTKM_CONT ArrayHandleSOA make_ArrayHandleSOA( std::initializer_list::ComponentType, @@ -377,16 +502,43 @@ VTKM_CONT ArrayHandleSOA make_ArrayHandleSOA( return ArrayHandleSOA(std::move(componentArrays)); } +/// @brief Create a `vtkm::cont::ArrayHandleSOA` with a number of array handles. +/// +/// This only works if all the templated arguments are of type +/// `vtkm::cont::ArrayHandle`. +/// +/// @code{.cpp} +/// vtkm::cont::ArrayHandle components1; +/// vtkm::cont::ArrayHandle components2; +/// vtkm::cont::ArrayHandle components3; +/// // Fill arrays... +/// +/// auto vecarray = +/// vtkm::cont::make_ArrayHandleSOA(components1, components2, components3); +/// @endcode template -VTKM_CONT - ArrayHandleSOA> - make_ArrayHandleSOA( - const vtkm::cont::ArrayHandle& componentArray0, - const RemainingArrays&... componentArrays) +VTKM_CONT ArrayHandleSOA< + vtkm::Vec::value>> +make_ArrayHandleSOA( + const vtkm::cont::ArrayHandle& componentArray0, + const RemainingArrays&... componentArrays) { return { componentArray0, componentArrays... }; } +/// @brief Create a `vtkm::cont::ArrayHandleSOA` with an initializer list of `std::vector`. +/// +/// The data is copied from the `std::vector`s to the array handle. +/// +/// @code{.cpp} +/// std::vector components1; +/// std::vector components2; +/// std::vector components3; +/// // Fill arrays... +/// +/// auto vecarray = vtkm::cont::make_ArrayHandleSOA>( +/// { components1, components2, components3 }); +/// @endcode template VTKM_CONT ArrayHandleSOA make_ArrayHandleSOA( std::initializer_list::ComponentType>>&& @@ -395,13 +547,28 @@ VTKM_CONT ArrayHandleSOA make_ArrayHandleSOA( return ArrayHandleSOA(std::move(componentVectors)); } -// This only works if all the templated arguments are of type std::vector. +/// @brief Create a `vtkm::cont::ArrayHandleSOA` with a number of `std::vector`. +/// +/// The first argument is a `vtkm::CopyFlag` to determine whether the input arrays +/// should be copied. +/// The component arrays are listed as arguments. +/// This only works if all the templated arguments are of type `std::vector`. +/// +/// @code{.cpp} +/// std::vector components1; +/// std::vector components2; +/// std::vector components3; +/// // Fill arrays... +/// +/// auto vecarray = vtkm::cont::make_ArrayHandleSOA( +/// vtkm::CopyFlag::On, components1, components2, components3); +/// @endcode template -VTKM_CONT - ArrayHandleSOA> - make_ArrayHandleSOA(vtkm::CopyFlag copy, - const std::vector& vector0, - RemainingVectors&&... componentVectors) +VTKM_CONT ArrayHandleSOA< + vtkm::Vec::value>> +make_ArrayHandleSOA(vtkm::CopyFlag copy, + const std::vector& vector0, + RemainingVectors&&... componentVectors) { // Convert std::vector to ArrayHandle first so that it correctly handles a mix of rvalue args. return { vtkm::cont::make_ArrayHandle(vector0, copy), @@ -409,32 +576,74 @@ VTKM_CONT copy)... }; } -// This only works if all the templated arguments are of type std::vector. +/// @brief Create a `vtkm::cont::ArrayHandleSOA` with a number of `std::vector`. +/// +/// The first argument is a `vtkm::CopyFlag` to determine whether the input arrays +/// should be copied. +/// The component arrays are listed as arguments. +/// This only works if all the templated arguments are rvalues of type +/// `std::vector`. +/// +/// @code{.cpp} +/// std::vector components1; +/// std::vector components2; +/// std::vector components3; +/// // Fill arrays... +/// +/// auto vecarray = vtkm::cont::make_ArrayHandleSOA(vtkm::CopyFlag::Off, +/// std::move(components1), +/// std::move(components2), +/// std::move(components3); +/// @endcode template -VTKM_CONT - ArrayHandleSOA> - make_ArrayHandleSOA(vtkm::CopyFlag copy, - std::vector&& vector0, - RemainingVectors&&... componentVectors) +VTKM_CONT ArrayHandleSOA< + vtkm::Vec::value>> +make_ArrayHandleSOA(vtkm::CopyFlag copy, + std::vector&& vector0, + RemainingVectors&&... componentVectors) { // Convert std::vector to ArrayHandle first so that it correctly handles a mix of rvalue args. return ArrayHandleSOA< - vtkm::Vec>( + vtkm::Vec::value>>( vtkm::cont::make_ArrayHandle(std::move(vector0), copy), vtkm::cont::make_ArrayHandle(std::forward(componentVectors), copy)...); } -// This only works if all the templated arguments are rvalues of std::vector. +/// @brief Create a `vtkm::cont::ArrayHandleSOA` with a number of `std::vector`. +/// +/// This only works if all the templated arguments are rvalues of type +/// `std::vector`. +/// +/// @code{.cpp} +/// std::vector components1; +/// std::vector components2; +/// std::vector components3; +/// // Fill arrays... +/// +/// auto vecarray = vtkm::cont::make_ArrayHandleSOAMove( +/// std::move(components1), std::move(components2), std::move(components3)); +/// @endcode template -VTKM_CONT - ArrayHandleSOA> - make_ArrayHandleSOAMove(std::vector&& vector0, - RemainingVectors&&... componentVectors) +VTKM_CONT ArrayHandleSOA< + vtkm::Vec::value>> +make_ArrayHandleSOAMove(std::vector&& vector0, + RemainingVectors&&... componentVectors) { return { vtkm::cont::make_ArrayHandleMove(std::move(vector0)), vtkm::cont::make_ArrayHandleMove(std::forward(componentVectors))... }; } +/// @brief Create a `vtkm::cont::ArrayHandleSOA` with an initializer list of C arrays. +/// +/// @code{.cpp} +/// T* components1; +/// T* components2; +/// T* components3; +/// // Fill arrays... +/// +/// auto vecarray = vtkm::cont::make_ArrayHandleSOA>( +/// { components1, components2, components3 }, size, vtkm::CopyFlag::On); +/// @endcode template VTKM_CONT ArrayHandleSOA make_ArrayHandleSOA( std::initializer_list::ComponentType*>&& @@ -445,14 +654,26 @@ VTKM_CONT ArrayHandleSOA make_ArrayHandleSOA( return ArrayHandleSOA(std::move(componentVectors), length, copy); } -// This only works if all the templated arguments are of type std::vector. +/// @brief Create a `vtkm::cont::ArrayHandleSOA` with a number of C arrays. +/// +/// This only works if all the templated arguments are of type `ComponentType*`. +/// +/// @code{.cpp} +/// T* components1; +/// T* components2; +/// T* components3; +/// // Fill arrays... +/// +/// auto vecarray = vtkm::cont::make_ArrayHandleSOA( +/// size, vtkm::CopyFlag::On, components1, components2, components3); +/// @endcode template -VTKM_CONT - ArrayHandleSOA> - make_ArrayHandleSOA(vtkm::Id length, - vtkm::CopyFlag copy, - const ComponentType* array0, - const RemainingArrays*... componentArrays) +VTKM_CONT ArrayHandleSOA< + vtkm::Vec::value>> +make_ArrayHandleSOA(vtkm::Id length, + vtkm::CopyFlag copy, + const ComponentType* array0, + const RemainingArrays*... componentArrays) { return ArrayHandleSOA< vtkm::Vec>( diff --git a/vtkm/cont/ArrayHandleStride.h b/vtkm/cont/ArrayHandleStride.h index 11cea2f04..3d0c33741 100644 --- a/vtkm/cont/ArrayHandleStride.h +++ b/vtkm/cont/ArrayHandleStride.h @@ -344,6 +344,7 @@ public: { } + /// @brief Construct an `ArrayHandleStride` from a basic array with specified access patterns. ArrayHandleStride(const vtkm::cont::ArrayHandle& array, vtkm::Id numValues, vtkm::Id stride, @@ -368,11 +369,44 @@ public: { } + /// @brief Get the stride that values are accessed. + /// + /// The stride is the spacing between consecutive values. The stride is measured + /// in terms of the number of values. A stride of 1 means a fully packed array. + /// A stride of 2 means selecting every other values. vtkm::Id GetStride() const { return StorageType::GetInfo(this->GetBuffers()).Stride; } + + /// @brief Get the offset to start reading values. + /// + /// The offset is the number of values to skip before the first value. The offset + /// is measured in terms of the number of values. An offset of 0 means the first value + /// at the beginning of the array. + /// + /// The offset is unaffected by the stride and dictates where the strides starts + /// counting. For example, given an array with size 3 vectors packed into an array, + /// a strided array referencing the middle component will have offset 1 and stride 3. vtkm::Id GetOffset() const { return StorageType::GetInfo(this->GetBuffers()).Offset; } + + /// @brief Get the modulus of the array index. + /// + /// When the index is modulo a value, it becomes the remainder after dividing by that + /// value. The effect of the modulus is to cause the index to repeat over the values + /// in the array. + /// + /// If the modulo is set to 0, then it is ignored. vtkm::Id GetModulo() const { return StorageType::GetInfo(this->GetBuffers()).Modulo; } + + /// @brief Get the divisor of the array index. + /// + /// The index is divided by the divisor before the other effects. The default divisor of + /// 1 will have no effect on the indexing. Setting the divisor to a value greater than 1 + /// has the effect of repeating each value that many times. vtkm::Id GetDivisor() const { return StorageType::GetInfo(this->GetBuffers()).Divisor; } + /// @brief Return the underlying data as a basic array handle. + /// + /// It is common for the same basic array to be shared among multiple + /// `vtkm::cont::ArrayHandleStride` objects. vtkm::cont::ArrayHandleBasic GetBasicArray() const { return StorageType::GetBasicArray(this->GetBuffers());