diff --git a/.gitlab/ci/ubuntu1604.yml b/.gitlab/ci/ubuntu1604.yml index 5ac715e6b..ded8bfe9f 100644 --- a/.gitlab/ci/ubuntu1604.yml +++ b/.gitlab/ci/ubuntu1604.yml @@ -56,7 +56,7 @@ build:ubuntu1604_gcc5_2: CC: "gcc-5" CXX: "g++-5" CMAKE_BUILD_TYPE: Release - VTKM_SETTINGS: "openmp+cuda+pascal+examples+static+use_virtuals" + VTKM_SETTINGS: "openmp+cuda+pascal+examples+static" test:ubuntu1804_test_ubuntu1604_gcc5_2: tags: diff --git a/benchmarking/BenchmarkInSitu.cxx b/benchmarking/BenchmarkInSitu.cxx new file mode 100644 index 000000000..b198de949 --- /dev/null +++ b/benchmarking/BenchmarkInSitu.cxx @@ -0,0 +1,958 @@ +//========================================================================== +// 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 "Benchmarker.h" + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace +{ + +const uint32_t DEFAULT_NUM_CYCLES = 20; +const vtkm::Id DEFAULT_DATASET_DIM = 128; +const vtkm::Id DEFAULT_IMAGE_SIZE = 1024; + +// Hold configuration state (e.g. active device): +vtkm::cont::InitializeResult Config; +// Input dataset dimensions: +vtkm::Id DataSetDim; +// Image size: +vtkm::Id ImageSize; +// The input datasets we'll use on the filters: +vtkm::cont::DataSet InputDataSet; +vtkm::cont::PartitionedDataSet PartitionedInputDataSet; +// The point scalars to use: +std::string PointScalarsName; +// The point vectors to use: +std::string PointVectorsName; + +enum class RenderingMode +{ + None = 0, + Mesh = 1, + RayTrace = 2, + Volume = 3, +}; + +std::vector ExtractDataSets(const vtkm::cont::PartitionedDataSet& partitions) +{ + return partitions.GetPartitions(); +} + +// Mirrors ExtractDataSet(ParitionedDataSet), to keep code simple at use sites +std::vector ExtractDataSets(vtkm::cont::DataSet& dataSet) +{ + return std::vector{ dataSet }; +} + +void BuildInputDataSet(uint32_t cycle, bool isStructured, bool isMultiBlock, vtkm::Id dim) +{ + vtkm::cont::PartitionedDataSet partitionedInputDataSet; + vtkm::cont::DataSet inputDataSet; + + PointScalarsName = "perlinnoise"; + PointVectorsName = "perlinnoisegrad"; + + // Generate uniform dataset(s) + const vtkm::Id3 dims{ dim, dim, dim }; + if (isMultiBlock) + { + for (auto i = 0; i < 2; ++i) + { + for (auto j = 0; j < 2; ++j) + { + for (auto k = 0; k < 2; ++k) + { + const vtkm::Vec3f origin{ static_cast(i), + static_cast(j), + static_cast(k) }; + const vtkm::source::PerlinNoise noise{ dims, + origin, + static_cast(cycle) }; + const auto dataset = noise.Execute(); + partitionedInputDataSet.AppendPartition(dataset); + } + } + } + } + else + { + const vtkm::source::PerlinNoise noise{ dims, static_cast(cycle) }; + inputDataSet = noise.Execute(); + } + + // Generate Perln Noise Gradient point vector field + vtkm::filter::Gradient gradientFilter; + gradientFilter.SetActiveField(PointScalarsName, vtkm::cont::Field::Association::POINTS); + gradientFilter.SetComputePointGradient(true); + gradientFilter.SetOutputFieldName(PointVectorsName); + gradientFilter.SetFieldsToPass( + vtkm::filter::FieldSelection(vtkm::filter::FieldSelection::MODE_ALL)); + if (isMultiBlock) + { + partitionedInputDataSet = gradientFilter.Execute(partitionedInputDataSet); + } + else + { + inputDataSet = gradientFilter.Execute(inputDataSet); + } + + // Run Tetrahedralize filter to convert uniform dataset(s) into unstructured ones + if (!isStructured) + { + vtkm::filter::Tetrahedralize destructizer; + destructizer.SetFieldsToPass( + vtkm::filter::FieldSelection(vtkm::filter::FieldSelection::MODE_ALL)); + if (isMultiBlock) + { + partitionedInputDataSet = destructizer.Execute(partitionedInputDataSet); + } + else + { + inputDataSet = destructizer.Execute(inputDataSet); + } + } + + // Release execution resources to simulate in-situ workload, where the data is + // not present in the execution environment + std::vector dataSets = + isMultiBlock ? ExtractDataSets(partitionedInputDataSet) : ExtractDataSets(inputDataSet); + for (auto& dataSet : dataSets) + { + dataSet.GetCellSet().ReleaseResourcesExecution(); + dataSet.GetCoordinateSystem().ReleaseResourcesExecution(); + dataSet.GetField(PointScalarsName).ReleaseResourcesExecution(); + dataSet.GetField(PointVectorsName).ReleaseResourcesExecution(); + } + + PartitionedInputDataSet = partitionedInputDataSet; + InputDataSet = inputDataSet; +} + +vtkm::rendering::Canvas* RenderDataSets(const std::vector& dataSets, + RenderingMode mode, + std::string fieldName) +{ + vtkm::rendering::Scene scene; + vtkm::cont::ColorTable colorTable("inferno"); + if (mode == RenderingMode::Volume) + { + colorTable.AddPointAlpha(0.0f, 0.03f); + colorTable.AddPointAlpha(1.0f, 0.01f); + } + + for (auto& dataSet : dataSets) + { + scene.AddActor(vtkm::rendering::Actor(dataSet.GetCellSet(), + dataSet.GetCoordinateSystem(), + dataSet.GetField(fieldName), + colorTable)); + } + + auto bounds = std::accumulate(dataSets.begin(), + dataSets.end(), + vtkm::Bounds(), + [=](const vtkm::Bounds& val, const vtkm::cont::DataSet& partition) { + return val + vtkm::cont::BoundsCompute(partition); + }); + vtkm::Vec3f_64 totalExtent(bounds.X.Length(), bounds.Y.Length(), bounds.Z.Length()); + vtkm::Float64 mag = vtkm::Magnitude(totalExtent); + vtkm::Normalize(totalExtent); + + // setup a camera and point it to towards the center of the input data + vtkm::rendering::Camera camera; + camera.SetFieldOfView(60.f); + camera.ResetToBounds(bounds); + camera.SetLookAt(totalExtent * (mag * .5f)); + camera.SetViewUp(vtkm::make_Vec(0.f, 1.f, 0.f)); + camera.SetPosition(totalExtent * (mag * 1.5f)); + + vtkm::rendering::CanvasRayTracer canvas(ImageSize, ImageSize); + + auto mapper = [=]() -> std::unique_ptr { + switch (mode) + { + case RenderingMode::Mesh: + { + return std::unique_ptr(new vtkm::rendering::MapperWireframer()); + } + case RenderingMode::RayTrace: + { + return std::unique_ptr(new vtkm::rendering::MapperRayTracer()); + } + case RenderingMode::Volume: + { + return std::unique_ptr(new vtkm::rendering::MapperVolume()); + } + case RenderingMode::None: + default: + { + return std::unique_ptr(new vtkm::rendering::MapperRayTracer()); + } + } + }(); + + vtkm::rendering::View3D view(scene, + *mapper, + canvas, + camera, + vtkm::rendering::Color(0.8f, 0.8f, 0.6f), + vtkm::rendering::Color(0.2f, 0.4f, 0.2f)); + view.Paint(); + + return view.GetCanvas().NewCopy(); +} + +void WriteToDisk(const vtkm::rendering::Canvas& canvas, + RenderingMode mode, + std::string bench, + bool isStructured, + bool isMultiBlock, + uint32_t cycle) +{ + std::ostringstream nameBuilder; + nameBuilder << "insitu_" << bench << "_" + << "cycle_" << cycle << "_" << (isStructured ? "structured_" : "unstructured_") + << (isMultiBlock ? "multi_" : "single_") + << (mode == RenderingMode::Mesh ? "mesh" + : (mode == RenderingMode::Volume ? "volume" : "ray")) + << ".png"; + canvas.SaveAs(nameBuilder.str()); +} + + +template +DataSetType RunContourHelper(vtkm::filter::Contour& filter, + vtkm::Id numIsoVals, + const DataSetType& input) +{ + // Set up some equally spaced contours, with the min/max slightly inside the + // scalar range: + const vtkm::Range scalarRange = + vtkm::cont::FieldRangeCompute(input, PointScalarsName).ReadPortal().Get(0); + const auto step = scalarRange.Length() / static_cast(numIsoVals + 1); + const auto minIsoVal = scalarRange.Min + (step * 0.5f); + filter.SetNumberOfIsoValues(numIsoVals); + for (vtkm::Id i = 0; i < numIsoVals; ++i) + { + filter.SetIsoValue(i, minIsoVal + (step * static_cast(i))); + } + + return filter.Execute(input); +} + +void BenchContour(::benchmark::State& state) +{ + const vtkm::cont::DeviceAdapterId device = Config.Device; + + const uint32_t cycle = static_cast(state.range(0)); + const vtkm::Id numIsoVals = static_cast(state.range(1)); + const bool isStructured = static_cast(state.range(2)); + const bool isMultiBlock = static_cast(state.range(3)); + const RenderingMode renderAlgo = static_cast(state.range(4)); + + vtkm::cont::Timer inputGenTimer{ device }; + inputGenTimer.Start(); + BuildInputDataSet(cycle, isStructured, isMultiBlock, DataSetDim); + inputGenTimer.Stop(); + + vtkm::filter::Contour filter; + filter.SetActiveField(PointScalarsName, vtkm::cont::Field::Association::POINTS); + filter.SetMergeDuplicatePoints(true); + filter.SetGenerateNormals(true); + filter.SetComputeFastNormalsForStructured(true); + filter.SetComputeFastNormalsForUnstructured(true); + + vtkm::cont::Timer totalTimer{ device }; + vtkm::cont::Timer filterTimer{ device }; + vtkm::cont::Timer renderTimer{ device }; + vtkm::cont::Timer writeTimer{ device }; + + for (auto _ : state) + { + (void)_; + totalTimer.Start(); + filterTimer.Start(); + std::vector dataSets; + if (isMultiBlock) + { + auto input = PartitionedInputDataSet; + auto result = RunContourHelper(filter, numIsoVals, input); + dataSets = ExtractDataSets(result); + } + else + { + auto input = InputDataSet; + auto result = RunContourHelper(filter, numIsoVals, input); + dataSets = ExtractDataSets(result); + } + filterTimer.Stop(); + + renderTimer.Start(); + auto canvas = RenderDataSets(dataSets, renderAlgo, PointScalarsName); + renderTimer.Stop(); + + writeTimer.Start(); + WriteToDisk(*canvas, renderAlgo, "contour", isStructured, isMultiBlock, cycle); + writeTimer.Stop(); + + totalTimer.Stop(); + + state.SetIterationTime(totalTimer.GetElapsedTime()); + state.counters.insert( + { { "InputGenTime", static_cast(inputGenTimer.GetElapsedTime() * 1000) }, + { "FilterTime", static_cast(filterTimer.GetElapsedTime() * 1000) }, + { "RenderTime", static_cast(renderTimer.GetElapsedTime() * 1000) }, + { "WriteTime", static_cast(writeTimer.GetElapsedTime() * 1000) } }); + } +} + +void BenchContourGenerator(::benchmark::internal::Benchmark* bm) +{ + bm->ArgNames({ "Cycle", "NIsos", "IsStructured", "IsMultiBlock", "RenderingMode" }); + + std::vector isStructureds{ false, true }; + std::vector isMultiBlocks{ false, true }; + std::vector renderingModes{ RenderingMode::RayTrace }; + for (uint32_t cycle = 1; cycle <= DEFAULT_NUM_CYCLES; ++cycle) + { + for (auto& isStructured : isStructureds) + { + for (auto& isMultiBlock : isMultiBlocks) + { + for (auto& mode : renderingModes) + { + bm->Args({ cycle, 10, isStructured, isMultiBlock, static_cast(mode) }); + } + } + } + } +} + +VTKM_BENCHMARK_APPLY(BenchContour, BenchContourGenerator); + +void MakeRandomSeeds(vtkm::Id seedCount, + vtkm::Bounds& bounds, + vtkm::cont::ArrayHandle& seeds) +{ + std::default_random_engine generator(static_cast(255)); + vtkm::FloatDefault zero(0), one(1); + std::uniform_real_distribution distribution(zero, one); + std::vector points; + points.resize(0); + for (vtkm::Id i = 0; i < seedCount; i++) + { + vtkm::FloatDefault rx = distribution(generator); + vtkm::FloatDefault ry = distribution(generator); + vtkm::FloatDefault rz = distribution(generator); + vtkm::Vec3f p; + p[0] = static_cast(bounds.X.Min + rx * bounds.X.Length()); + p[1] = static_cast(bounds.Y.Min + ry * bounds.Y.Length()); + p[2] = static_cast(bounds.Z.Min + rz * bounds.Z.Length()); + points.push_back(vtkm::Particle(p, static_cast(i))); + } + vtkm::cont::ArrayHandle tmp = + vtkm::cont::make_ArrayHandle(points, vtkm::CopyFlag::Off); + vtkm::cont::ArrayCopy(tmp, seeds); +} + +vtkm::Id GetNumberOfPoints(const vtkm::cont::DataSet& input) +{ + return input.GetCoordinateSystem().GetNumberOfPoints(); +} + +vtkm::Id GetNumberOfPoints(const vtkm::cont::PartitionedDataSet& input) +{ + return input.GetPartition(0).GetCoordinateSystem().GetNumberOfPoints(); +} + +void AddField(vtkm::cont::DataSet& input, + std::string fieldName, + std::vector& field) +{ + input.AddPointField(fieldName, field); +} + +void AddField(vtkm::cont::PartitionedDataSet& input, + std::string fieldName, + std::vector& field) +{ + for (auto i = 0; i < input.GetNumberOfPartitions(); ++i) + { + auto partition = input.GetPartition(i); + AddField(partition, fieldName, field); + input.ReplacePartition(i, partition); + } +} + +template +DataSetType RunStreamlinesHelper(vtkm::filter::Streamline& filter, const DataSetType& input) +{ + auto dataSetBounds = vtkm::cont::BoundsCompute(input); + vtkm::cont::ArrayHandle seedArray; + MakeRandomSeeds(100, dataSetBounds, seedArray); + filter.SetSeeds(seedArray); + + auto result = filter.Execute(input); + auto numPoints = GetNumberOfPoints(result); + std::vector colorMap( + static_cast::size_type>(numPoints)); + for (std::vector::size_type i = 0; i < colorMap.size(); i++) + { + colorMap[i] = static_cast(i); + } + + AddField(result, "pointvar", colorMap); + return result; +} + +void BenchStreamlines(::benchmark::State& state) +{ + const vtkm::cont::DeviceAdapterId device = Config.Device; + + const uint32_t cycle = static_cast(state.range(0)); + const bool isStructured = static_cast(state.range(1)); + const bool isMultiBlock = static_cast(state.range(2)); + const RenderingMode renderAlgo = static_cast(state.range(3)); + + vtkm::cont::Timer inputGenTimer{ device }; + inputGenTimer.Start(); + BuildInputDataSet(cycle, isStructured, isMultiBlock, DataSetDim); + inputGenTimer.Stop(); + + vtkm::filter::Streamline streamline; + streamline.SetStepSize(0.1f); + streamline.SetNumberOfSteps(1000); + streamline.SetActiveField(PointVectorsName); + + vtkm::cont::Timer totalTimer{ device }; + vtkm::cont::Timer filterTimer{ device }; + vtkm::cont::Timer renderTimer{ device }; + vtkm::cont::Timer writeTimer{ device }; + + for (auto _ : state) + { + (void)_; + totalTimer.Start(); + filterTimer.Start(); + + std::vector dataSets; + if (isMultiBlock) + { + auto input = PartitionedInputDataSet; + auto result = RunStreamlinesHelper(streamline, input); + dataSets = ExtractDataSets(result); + } + else + { + auto input = InputDataSet; + auto result = RunStreamlinesHelper(streamline, input); + dataSets = ExtractDataSets(result); + } + filterTimer.Stop(); + + renderTimer.Start(); + auto canvas = RenderDataSets(dataSets, renderAlgo, "pointvar"); + renderTimer.Stop(); + + writeTimer.Start(); + WriteToDisk(*canvas, renderAlgo, "streamlines", isStructured, isMultiBlock, cycle); + writeTimer.Stop(); + + totalTimer.Stop(); + + state.SetIterationTime(totalTimer.GetElapsedTime()); + state.counters.insert( + { { "InputGenTime", static_cast(inputGenTimer.GetElapsedTime() * 1000) }, + { "FilterTime", static_cast(filterTimer.GetElapsedTime() * 1000) }, + { "RenderTime", static_cast(renderTimer.GetElapsedTime() * 1000) }, + { "WriteTime", static_cast(writeTimer.GetElapsedTime() * 1000) } }); + } +} + +void BenchStreamlinesGenerator(::benchmark::internal::Benchmark* bm) +{ + bm->ArgNames({ "Cycle", "IsStructured", "IsMultiBlock", "RenderingMode" }); + + std::vector isStructureds{ false, true }; + std::vector isMultiBlocks{ false, true }; + std::vector renderingModes{ RenderingMode::Mesh }; + for (uint32_t cycle = 1; cycle <= DEFAULT_NUM_CYCLES; ++cycle) + { + for (auto& isStructured : isStructureds) + { + for (auto& isMultiBlock : isMultiBlocks) + { + for (auto& mode : renderingModes) + { + bm->Args({ cycle, isStructured, isMultiBlock, static_cast(mode) }); + } + } + } + } +} + +VTKM_BENCHMARK_APPLY(BenchStreamlines, BenchStreamlinesGenerator); + +vtkm::Vec3f GetSlicePlaneOrigin(const bool isMultiBlock) +{ + if (isMultiBlock) + { + auto data = PartitionedInputDataSet; + vtkm::Bounds global; + global = data.GetPartition(0).GetCoordinateSystem().GetBounds(); + for (auto i = 1; i < data.GetNumberOfPartitions(); ++i) + { + auto dataset = data.GetPartition(i); + vtkm::Bounds bounds = dataset.GetCoordinateSystem().GetBounds(); + global.X.Min = vtkm::Min(global.X.Min, bounds.X.Min); + global.Y.Min = vtkm::Min(global.Y.Min, bounds.Y.Min); + global.Z.Min = vtkm::Min(global.Z.Min, bounds.Z.Min); + global.X.Max = vtkm::Min(global.X.Max, bounds.X.Max); + global.Y.Max = vtkm::Min(global.Y.Max, bounds.Y.Max); + global.Z.Max = vtkm::Min(global.Z.Max, bounds.Z.Max); + } + return vtkm::Vec3f{ static_cast((global.X.Max - global.X.Min) / 2.), + static_cast((global.Y.Max - global.Y.Min) / 2.), + static_cast((global.Z.Max - global.Z.Min) / 2.) }; + } + else + { + auto data = InputDataSet; + vtkm::Bounds bounds = data.GetCoordinateSystem().GetBounds(); + return vtkm::Vec3f{ static_cast((bounds.X.Max - bounds.X.Min) / 2.), + static_cast((bounds.Y.Max - bounds.Y.Min) / 2.), + static_cast((bounds.Z.Max - bounds.Z.Min) / 2.) }; + } +} + +void BenchSlice(::benchmark::State& state) +{ + const vtkm::cont::DeviceAdapterId device = Config.Device; + + const uint32_t cycle = static_cast(state.range(0)); + const bool isStructured = static_cast(state.range(1)); + const bool isMultiBlock = static_cast(state.range(2)); + const RenderingMode renderAlgo = static_cast(state.range(3)); + + vtkm::cont::Timer inputGenTimer{ device }; + inputGenTimer.Start(); + BuildInputDataSet(cycle, isStructured, isMultiBlock, DataSetDim); + inputGenTimer.Stop(); + + vtkm::filter::Slice filter; + + vtkm::cont::Timer totalTimer{ device }; + vtkm::cont::Timer filterTimer{ device }; + vtkm::cont::Timer renderTimer{ device }; + vtkm::cont::Timer writeTimer{ device }; + + for (auto _ : state) + { + (void)_; + totalTimer.Start(); + filterTimer.Start(); + std::vector dataSets; + if (isMultiBlock) + { + auto input = PartitionedInputDataSet; + vtkm::Vec3f origin = GetSlicePlaneOrigin(isMultiBlock); + // Set-up implicit function + vtkm::Plane plane(origin, vtkm::Plane::Vector{ 1, 1, 1 }); + filter.SetImplicitFunction(plane); + auto result = filter.Execute(input); + dataSets = ExtractDataSets(result); + } + else + { + auto input = InputDataSet; + vtkm::Vec3f origin = GetSlicePlaneOrigin(isMultiBlock); + // Set-up implicit function + vtkm::Plane plane(origin, vtkm::Plane::Vector{ 1, 1, 1 }); + filter.SetImplicitFunction(plane); + auto result = filter.Execute(input); + dataSets = ExtractDataSets(result); + } + filterTimer.Stop(); + + renderTimer.Start(); + auto canvas = RenderDataSets(dataSets, renderAlgo, PointScalarsName); + renderTimer.Stop(); + + writeTimer.Start(); + WriteToDisk(*canvas, renderAlgo, "slice", isStructured, isMultiBlock, cycle); + writeTimer.Stop(); + + totalTimer.Stop(); + + state.SetIterationTime(totalTimer.GetElapsedTime()); + state.counters.insert( + { { "InputGenTime", static_cast(inputGenTimer.GetElapsedTime() * 1000) }, + { "FilterTime", static_cast(filterTimer.GetElapsedTime() * 1000) }, + { "RenderTime", static_cast(renderTimer.GetElapsedTime() * 1000) }, + { "WriteTime", static_cast(writeTimer.GetElapsedTime() * 1000) } }); + } +} + +void BenchSliceGenerator(::benchmark::internal::Benchmark* bm) +{ + bm->ArgNames({ "Cycle", "IsStructured", "IsMultiBlock", "RenderingMode" }); + + std::vector isStructureds{ false, true }; + std::vector isMultiBlocks{ false, true }; + std::vector renderingModes{ RenderingMode::RayTrace }; + for (uint32_t cycle = 1; cycle <= DEFAULT_NUM_CYCLES; ++cycle) + { + for (auto& isStructured : isStructureds) + { + for (auto& isMultiBlock : isMultiBlocks) + { + for (auto& mode : renderingModes) + { + bm->Args({ cycle, isStructured, isMultiBlock, static_cast(mode) }); + } + } + } + } +} + +VTKM_BENCHMARK_APPLY(BenchSlice, BenchSliceGenerator); + +void BenchMeshRendering(::benchmark::State& state) +{ + const vtkm::cont::DeviceAdapterId device = Config.Device; + + const uint32_t cycle = static_cast(state.range(0)); + const bool isStructured = static_cast(state.range(1)); + const bool isMultiBlock = static_cast(state.range(2)); + + vtkm::cont::Timer inputGenTimer{ device }; + vtkm::cont::Timer renderTimer{ device }; + vtkm::cont::Timer writeTimer{ device }; + + inputGenTimer.Start(); + BuildInputDataSet(cycle, isStructured, isMultiBlock, DataSetDim); + inputGenTimer.Stop(); + + vtkm::cont::Timer totalTimer{ device }; + + for (auto _ : state) + { + (void)_; + + totalTimer.Start(); + + std::vector dataSets = + isMultiBlock ? ExtractDataSets(PartitionedInputDataSet) : ExtractDataSets(InputDataSet); + + renderTimer.Start(); + auto canvas = RenderDataSets(dataSets, RenderingMode::Mesh, PointScalarsName); + renderTimer.Stop(); + + writeTimer.Start(); + WriteToDisk(*canvas, RenderingMode::Mesh, "mesh", isStructured, isMultiBlock, cycle); + writeTimer.Stop(); + + totalTimer.Stop(); + + state.SetIterationTime(totalTimer.GetElapsedTime()); + state.counters.insert( + { { "InputGenTime", static_cast(inputGenTimer.GetElapsedTime() * 1000) }, + { "FilterTime", 0 }, + { "RenderTime", static_cast(renderTimer.GetElapsedTime() * 1000) }, + { "WriteTime", static_cast(writeTimer.GetElapsedTime() * 1000) } }); + } +} + +void BenchMeshRenderingGenerator(::benchmark::internal::Benchmark* bm) +{ + bm->ArgNames({ "Cycle", "IsStructured", "IsMultiBlock" }); + + std::vector isStructureds{ false, true }; + std::vector isMultiBlocks{ false, true }; + for (uint32_t cycle = 1; cycle <= DEFAULT_NUM_CYCLES; ++cycle) + { + for (auto& isStructured : isStructureds) + { + for (auto& isMultiBlock : isMultiBlocks) + { + bm->Args({ cycle, isStructured, isMultiBlock }); + } + } + } +} + +VTKM_BENCHMARK_APPLY(BenchMeshRendering, BenchMeshRenderingGenerator); + +void BenchVolumeRendering(::benchmark::State& state) +{ + const vtkm::cont::DeviceAdapterId device = Config.Device; + + const uint32_t cycle = static_cast(state.range(0)); + const bool isStructured = true; + const bool isMultiBlock = static_cast(state.range(1)); + + vtkm::cont::Timer inputGenTimer{ device }; + inputGenTimer.Start(); + BuildInputDataSet(cycle, isStructured, isMultiBlock, DataSetDim); + inputGenTimer.Stop(); + + vtkm::cont::Timer totalTimer{ device }; + vtkm::cont::Timer renderTimer{ device }; + vtkm::cont::Timer writeTimer{ device }; + + for (auto _ : state) + { + (void)_; + totalTimer.Start(); + + renderTimer.Start(); + std::vector dataSets = + isMultiBlock ? ExtractDataSets(PartitionedInputDataSet) : ExtractDataSets(InputDataSet); + auto canvas = RenderDataSets(dataSets, RenderingMode::Volume, PointScalarsName); + renderTimer.Stop(); + + writeTimer.Start(); + WriteToDisk(*canvas, RenderingMode::Volume, "volume", isStructured, isMultiBlock, cycle); + writeTimer.Stop(); + + totalTimer.Stop(); + + state.SetIterationTime(totalTimer.GetElapsedTime()); + state.counters.insert( + { { "InputGenTime", static_cast(inputGenTimer.GetElapsedTime() * 1000) }, + { "FilterTime", 0 }, + { "RenderTime", static_cast(renderTimer.GetElapsedTime() * 1000) }, + { "WriteTime", static_cast(writeTimer.GetElapsedTime() * 1000) } }); + } +} + +void BenchVolumeRenderingGenerator(::benchmark::internal::Benchmark* bm) +{ + bm->ArgNames({ "Cycle", "IsMultiBlock" }); + + std::vector isMultiBlocks{ false }; + for (uint32_t cycle = 1; cycle <= DEFAULT_NUM_CYCLES; ++cycle) + { + for (auto& isMultiBlock : isMultiBlocks) + { + bm->Args({ cycle, isMultiBlock }); + } + } +} + +VTKM_BENCHMARK_APPLY(BenchVolumeRendering, BenchVolumeRenderingGenerator); + +struct Arg : vtkm::cont::internal::option::Arg +{ + static vtkm::cont::internal::option::ArgStatus Number( + const vtkm::cont::internal::option::Option& option, + bool msg) + { + bool argIsNum = ((option.arg != nullptr) && (option.arg[0] != '\0')); + const char* c = option.arg; + while (argIsNum && (*c != '\0')) + { + argIsNum &= static_cast(std::isdigit(*c)); + ++c; + } + + if (argIsNum) + { + return vtkm::cont::internal::option::ARG_OK; + } + else + { + if (msg) + { + std::cerr << "Option " << option.name << " requires a numeric argument." << std::endl; + } + + return vtkm::cont::internal::option::ARG_ILLEGAL; + } + } +}; + +enum OptionIndex +{ + UNKNOWN, + HELP, + DATASET_DIM, + IMAGE_SIZE, +}; + +void ParseBenchmarkOptions(int& argc, char** argv) +{ + namespace option = vtkm::cont::internal::option; + + std::vector usage; + std::string usageHeader{ "Usage: " }; + usageHeader.append(argv[0]); + usageHeader.append("[input data options] [benchmark options]"); + usage.push_back({ UNKNOWN, 0, "", "", Arg::None, usageHeader.c_str() }); + usage.push_back({ UNKNOWN, 0, "", "", Arg::None, "Input data options are:" }); + usage.push_back({ HELP, 0, "h", "help", Arg::None, " -h, --help\tDisplay this help." }); + usage.push_back({ UNKNOWN, 0, "", "", Arg::None, Config.Usage.c_str() }); + usage.push_back({ DATASET_DIM, + 0, + "s", + "size", + Arg::Number, + " -s, --size \tSpecify dataset dimension and " + "dataset with NxNxN dimensions is created. " + "If not specified, N=128" }); + usage.push_back({ IMAGE_SIZE, + 0, + "i", + "image-size", + Arg::Number, + " -i, --image-size \tSpecify size of the rendered image." + " The image is rendered as a square of size NxN. " + "If not specified, N=1024" }); + usage.push_back({ 0, 0, nullptr, nullptr, nullptr, nullptr }); + + option::Stats stats(usage.data(), argc - 1, argv + 1); + std::unique_ptr options{ new option::Option[stats.options_max] }; + std::unique_ptr buffer{ new option::Option[stats.buffer_max] }; + option::Parser commandLineParse(usage.data(), argc - 1, argv + 1, options.get(), buffer.get()); + + if (options[HELP]) + { + option::printUsage(std::cout, usage.data()); + // Print google benchmark usage too + const char* helpstr = "--help"; + char* tmpargv[] = { argv[0], const_cast(helpstr), nullptr }; + int tmpargc = 2; + VTKM_EXECUTE_BENCHMARKS(tmpargc, tmpargv); + exit(0); + } + if (options[DATASET_DIM]) + { + std::istringstream parse(options[DATASET_DIM].arg); + parse >> DataSetDim; + } + else + { + DataSetDim = DEFAULT_DATASET_DIM; + } + if (options[IMAGE_SIZE]) + { + std::istringstream parse(options[IMAGE_SIZE].arg); + parse >> ImageSize; + } + else + { + ImageSize = DEFAULT_IMAGE_SIZE; + } + + std::cerr << "Using data set dimensions = " << DataSetDim << std::endl; + std::cerr << "Using image size = " << ImageSize << "x" << ImageSize << std::endl; + + // Now go back through the arg list and remove anything that is not in the list of + // unknown options or non-option arguments. + int destArg = 1; + // This is copy/pasted from vtkm::cont::Initialize(), should probably be abstracted eventually: + for (int srcArg = 1; srcArg < argc; ++srcArg) + { + std::string thisArg{ argv[srcArg] }; + bool copyArg = false; + + // Special case: "--" gets removed by optionparser but should be passed. + if (thisArg == "--") + { + copyArg = true; + } + for (const option::Option* opt = options[UNKNOWN]; !copyArg && opt != nullptr; + opt = opt->next()) + { + if (thisArg == opt->name) + { + copyArg = true; + } + if ((opt->arg != nullptr) && (thisArg == opt->arg)) + { + copyArg = true; + } + // Special case: optionparser sometimes removes a single "-" from an option + if (thisArg.substr(1) == opt->name) + { + copyArg = true; + } + } + for (int nonOpt = 0; !copyArg && nonOpt < commandLineParse.nonOptionsCount(); ++nonOpt) + { + if (thisArg == commandLineParse.nonOption(nonOpt)) + { + copyArg = true; + } + } + if (copyArg) + { + if (destArg != srcArg) + { + argv[destArg] = argv[srcArg]; + } + ++destArg; + } + } + argc = destArg; +} + +} // end anon namespace + +int main(int argc, char* argv[]) +{ + auto opts = vtkm::cont::InitializeOptions::RequireDevice; + + std::vector args(argv, argv + argc); + vtkm::bench::detail::InitializeArgs(&argc, args, opts); + + // Parse VTK-m options + Config = vtkm::cont::Initialize(argc, args.data(), opts); + ParseBenchmarkOptions(argc, args.data()); + + // This opts chances when it is help + if (opts != vtkm::cont::InitializeOptions::None) + { + vtkm::cont::GetRuntimeDeviceTracker().ForceDevice(Config.Device); + } + + VTKM_EXECUTE_BENCHMARKS(argc, args.data()); +} diff --git a/benchmarking/CMakeLists.txt b/benchmarking/CMakeLists.txt index 3b59cad74..2cf18da0c 100644 --- a/benchmarking/CMakeLists.txt +++ b/benchmarking/CMakeLists.txt @@ -71,4 +71,5 @@ endif() if(TARGET vtkm_rendering) add_benchmark(NAME BenchmarkRayTracing FILE BenchmarkRayTracing.cxx LIBS vtkm_rendering vtkm_source) + add_benchmark(NAME BenchmarkInSitu FILE BenchmarkInSitu.cxx LIBS vtkm_rendering vtkm_source vtkm_filter vtkm_io) endif() diff --git a/version.txt b/version.txt index 57dfe270d..bd8bf882d 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.7.0-rc1 +1.7.0 diff --git a/vtkm/cont/cuda/ChooseCudaDevice.h b/vtkm/cont/cuda/ChooseCudaDevice.h index ae3a215db..2928fe7a9 100644 --- a/vtkm/cont/cuda/ChooseCudaDevice.h +++ b/vtkm/cont/cuda/ChooseCudaDevice.h @@ -14,8 +14,8 @@ #include #include +#include #include -#include #include #include diff --git a/vtkm/source/CMakeLists.txt b/vtkm/source/CMakeLists.txt index 88c576b2c..03de54190 100644 --- a/vtkm/source/CMakeLists.txt +++ b/vtkm/source/CMakeLists.txt @@ -13,6 +13,7 @@ set(headers Source.h Tangle.h Wavelet.h + PerlinNoise.h ) set(device_sources @@ -20,6 +21,7 @@ set(device_sources Source.cxx Tangle.cxx Wavelet.cxx + PerlinNoise.cxx ) vtkm_library(NAME vtkm_source diff --git a/vtkm/source/PerlinNoise.cxx b/vtkm/source/PerlinNoise.cxx new file mode 100644 index 000000000..03847536b --- /dev/null +++ b/vtkm/source/PerlinNoise.cxx @@ -0,0 +1,209 @@ +//============================================================================ +// 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 vtkm +{ +namespace source +{ +namespace perlin +{ +struct PerlinNoiseWorklet : public vtkm::worklet::WorkletVisitPointsWithCells +{ + using ControlSignature = void(CellSetIn, FieldInPoint, WholeArrayIn, FieldOut noise); + using ExecutionSignature = void(_2, _3, _4); + + VTKM_CONT PerlinNoiseWorklet(vtkm::Id repeat) + : Repeat(repeat) + { + } + + // Adapted from https://adrianb.io/2014/08/09/perlinnoise.html + // Archive link: https://web.archive.org/web/20210329174559/https://adrianb.io/2014/08/09/perlinnoise.html + template + VTKM_EXEC void operator()(const PointVecType& pos, const PermsPortal& perms, OutType& noise) const + { + vtkm::Id xi = static_cast(pos[0]) % this->Repeat; + vtkm::Id yi = static_cast(pos[1]) % this->Repeat; + vtkm::Id zi = static_cast(pos[2]) % this->Repeat; + vtkm::FloatDefault xf = pos[0] - xi; + vtkm::FloatDefault yf = pos[1] - yi; + vtkm::FloatDefault zf = pos[2] - zi; + vtkm::FloatDefault u = this->Fade(xf); + vtkm::FloatDefault v = this->Fade(yf); + vtkm::FloatDefault w = this->Fade(zf); + + vtkm::Id aaa, aba, aab, abb, baa, bba, bab, bbb; + aaa = perms[perms[perms[xi] + yi] + zi]; + aba = perms[perms[perms[xi] + this->Increment(yi)] + zi]; + aab = perms[perms[perms[xi] + yi] + this->Increment(zi)]; + abb = perms[perms[perms[xi] + this->Increment(yi)] + this->Increment(zi)]; + baa = perms[perms[perms[this->Increment(xi)] + yi] + zi]; + bba = perms[perms[perms[this->Increment(xi)] + this->Increment(yi)] + zi]; + bab = perms[perms[perms[this->Increment(xi)] + yi] + this->Increment(zi)]; + bbb = perms[perms[perms[this->Increment(xi)] + this->Increment(yi)] + this->Increment(zi)]; + + vtkm::FloatDefault x1, x2, y1, y2; + x1 = vtkm::Lerp(this->Gradient(aaa, xf, yf, zf), this->Gradient(baa, xf - 1, yf, zf), u); + x2 = + vtkm::Lerp(this->Gradient(aba, xf, yf - 1, zf), this->Gradient(bba, xf - 1, yf - 1, zf), u); + y1 = vtkm::Lerp(x1, x2, v); + + x1 = + vtkm::Lerp(this->Gradient(aab, xf, yf, zf - 1), this->Gradient(bab, xf - 1, yf, zf - 1), u); + x2 = vtkm::Lerp( + this->Gradient(abb, xf, yf - 1, zf - 1), this->Gradient(bbb, xf - 1, yf - 1, zf - 1), u); + y2 = vtkm::Lerp(x1, x2, v); + + noise = (vtkm::Lerp(y1, y2, w) + OutType(1.0f)) * OutType(0.5f); + } + + VTKM_EXEC vtkm::FloatDefault Fade(vtkm::FloatDefault t) const + { + return t * t * t * (t * (t * 6 - 15) + 10); + } + + VTKM_EXEC vtkm::Id Increment(vtkm::Id n) const { return (n + 1) % this->Repeat; } + + VTKM_EXEC vtkm::FloatDefault Gradient(vtkm::Id hash, + vtkm::FloatDefault x, + vtkm::FloatDefault y, + vtkm::FloatDefault z) const + { + switch (hash & 0xF) + { + case 0x0: + return x + y; + case 0x1: + return -x + y; + case 0x2: + return x - y; + case 0x3: + return -x - y; + case 0x4: + return x + z; + case 0x5: + return -x + z; + case 0x6: + return x - z; + case 0x7: + return -x - z; + case 0x8: + return y + z; + case 0x9: + return -y + z; + case 0xA: + return y - z; + case 0xB: + return -y - z; + case 0xC: + return y + x; + case 0xD: + return -y + z; + case 0xE: + return y - x; + case 0xF: + return -y - z; + default: + return 0; // never happens + } + } + + vtkm::Id Repeat; +}; + +class PerlinNoiseField : public vtkm::filter::FilterField +{ +public: + VTKM_CONT PerlinNoiseField(vtkm::IdComponent tableSize, vtkm::Id seed) + : TableSize(tableSize) + , Seed(seed) + { + this->GeneratePermutations(); + this->SetUseCoordinateSystemAsField(true); + } + + template + VTKM_CONT vtkm::cont::DataSet DoExecute( + const vtkm::cont::DataSet& input, + const FieldType& vtkmNotUsed(field), + const vtkm::filter::FieldMetadata& fieldMetadata, + vtkm::filter::PolicyBase vtkmNotUsed(policy)) + { + vtkm::cont::ArrayHandle noise; + PerlinNoiseWorklet worklet{ this->TableSize }; + this->Invoke( + worklet, input.GetCellSet(), input.GetCoordinateSystem(), this->Permutations, noise); + + return vtkm::filter::CreateResult(input, noise, this->GetOutputFieldName(), fieldMetadata); + } + +private: + VTKM_CONT void GeneratePermutations() + { + std::mt19937_64 rng; + rng.seed(this->Seed); + std::uniform_int_distribution distribution(0, this->TableSize - 1); + + vtkm::cont::ArrayHandle perms; + perms.Allocate(this->TableSize); + auto permsPortal = perms.WritePortal(); + for (auto i = 0; i < permsPortal.GetNumberOfValues(); ++i) + { + permsPortal.Set(i, distribution(rng)); + } + this->Permutations.Allocate(2 * this->TableSize); + auto permutations = this->Permutations.WritePortal(); + for (auto i = 0; i < permutations.GetNumberOfValues(); ++i) + { + permutations.Set(i, permsPortal.Get(i % this->TableSize)); + } + } + + vtkm::IdComponent TableSize; + vtkm::Id Seed; + vtkm::cont::ArrayHandle Permutations; +}; +} // namespace perlin + +vtkm::cont::DataSet PerlinNoise::Execute() const +{ + VTKM_LOG_SCOPE_FUNCTION(vtkm::cont::LogLevel::Perf); + + vtkm::cont::DataSet dataSet; + const vtkm::Id3 pdims{ this->Dims + vtkm::Id3{ 1, 1, 1 } }; + const vtkm::Vec3f spacing(1.0f / static_cast(this->Dims[0]), + 1.0f / static_cast(this->Dims[1]), + 1.0f / static_cast(this->Dims[2])); + + + vtkm::cont::CellSetStructured<3> cellSet; + cellSet.SetPointDimensions(pdims); + dataSet.SetCellSet(cellSet); + vtkm::cont::ArrayHandleUniformPointCoordinates coordinates(pdims, this->Origin, spacing); + dataSet.AddCoordinateSystem(vtkm::cont::CoordinateSystem("coordinates", coordinates)); + + auto tableSize = static_cast( + vtkm::Max(this->Dims[0], vtkm::Max(this->Dims[1], this->Dims[2]))); + perlin::PerlinNoiseField noiseGenerator(tableSize, this->Seed); + noiseGenerator.SetOutputFieldName("perlinnoise"); + dataSet = noiseGenerator.Execute(dataSet); + + return dataSet; +} + +} // namespace source +} // namespace vtkm diff --git a/vtkm/source/PerlinNoise.h b/vtkm/source/PerlinNoise.h new file mode 100644 index 000000000..3f54503a9 --- /dev/null +++ b/vtkm/source/PerlinNoise.h @@ -0,0 +1,61 @@ +//============================================================================ +// 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_source_PerlinNoise_h +#define vtk_m_source_PerlinNoise_h + +#include + +namespace vtkm +{ +namespace source +{ +/** + * @brief The PerlinNoise source creates a uniform dataset. + * + * This class generates a uniform grid dataset with a tileable perlin + * noise scalar point field. + * + * The Execute method creates a complete structured dataset that have a + * scalar point field named 'perlinnoise'. +**/ +class VTKM_SOURCE_EXPORT PerlinNoise final : public vtkm::source::Source +{ +public: + ///Construct a PerlinNoise with Cell Dimensions + VTKM_CONT + PerlinNoise(vtkm::Id3 dims, vtkm::IdComponent seed) + : PerlinNoise(dims, vtkm::Vec3f(0), seed) + { + } + + VTKM_CONT + PerlinNoise(vtkm::Id3 dims, vtkm::Vec3f origin, vtkm::IdComponent seed) + : Dims(dims) + , Origin(origin) + , Seed(seed) + { + } + + vtkm::IdComponent GetSeed() const { return this->Seed; } + + void SetSeed(vtkm::IdComponent seed) { this->Seed = seed; } + + vtkm::cont::DataSet Execute() const override; + +private: + vtkm::Id3 Dims; + vtkm::Vec3f Origin; + vtkm::IdComponent Seed; +}; +} //namespace source +} //namespace vtkm + +#endif //vtk_m_source_PerlinNoise_h