Add FrontierBenchmark as an example

This commit is contained in:
Manish Mathai 2023-03-12 18:10:43 -07:00
parent a6856a3f3a
commit 4cae295594
11 changed files with 420 additions and 38 deletions

@ -52,6 +52,8 @@ if(VTKm_ENABLE_EXAMPLES)
add_subdirectory(temporal_advection)
add_subdirectory(tetrahedra)
add_subdirectory(smoke_test)
add_subdirectory(frontier_benchmark)
endif()
if (VTKm_ENABLE_TESTING)

@ -0,0 +1,19 @@
##============================================================================
## 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.
##============================================================================
cmake_minimum_required(VERSION 3.12...3.15 FATAL_ERROR)
project(VTKmFrontierBenchmark CXX)
#Find the VTK-m package
find_package(VTKm REQUIRED QUIET)
if(TARGET vtkm::rendering AND TARGET vtkm::filter_contour AND TARGET vtkm::source)
add_executable(FrontierBenchmark FrontierBenchmark.cxx)
target_link_libraries(FrontierBenchmark PRIVATE vtkm::rendering vtkm::filter_contour vtkm::source)
endif()

@ -0,0 +1,322 @@
//============================================================================
// 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.
//============================================================================
//============================================================================
// 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 <vtkm/Bounds.h>
#include <vtkm/cont/Timer.h>
#include <vtkm/filter/contour/Contour.h>
#include <vtkm/rendering/Actor.h>
#include <vtkm/rendering/CanvasRayTracer.h>
#include <vtkm/rendering/MapperRayTracer.h>
#include <vtkm/rendering/Scene.h>
#include <vtkm/rendering/View3D.h>
#include <vtkm/rendering/compositing/Image.h>
#include <vtkm/rendering/testing/RenderTest.h>
#include <vtkm/source/PerlinNoise.h>
struct BenchmarkOptions
{
BenchmarkOptions(int argc, char** argv) { this->Parse(argc, argv); }
void Parse(int argc, char** argv)
{
for (int i = 1; i < argc; ++i)
{
auto arg = std::string(argv[i]);
auto seperatorPos = arg.find('=');
if (seperatorPos == std::string::npos)
{
continue;
}
auto key = arg.substr(0, seperatorPos);
auto val = arg.substr(seperatorPos + 1);
if (key == "--perlin-dims")
{
int start = 0;
for (int d = 0; d < 3; ++d)
{
auto end = val.find(',', start);
auto dim = val.substr(start, end - start);
this->PerlinDimensions[d] = std::stoi(dim);
start = end + 1;
}
}
else if (key == "--perlin-seed")
{
this->PerlinSeed = std::stoi(val);
}
else if (key == "--width")
{
this->CanvasWidth = std::stoi(val);
}
else if (key == "--height")
{
this->CanvasHeight = std::stoi(val);
}
else if (key == "--iters")
{
this->NumIterations = std::stoi(val);
}
else if (key == "--timing-file")
{
this->TimingFileName = val;
}
}
}
vtkm::Id3 PerlinDimensions = vtkm::Id3(1024, 1024, 1024);
vtkm::IdComponent PerlinSeed = 1;
vtkm::Id CanvasWidth = 1920;
vtkm::Id CanvasHeight = 1080;
vtkm::Id NumIterations = 10;
std::string TimingFileName = "timing.csv";
};
struct MpiTopology
{
MpiTopology(int rank, int size)
: Rank(rank)
, Size(size)
{
}
int Rank;
int Size;
int XRank;
int YRank;
int ZRank;
int XSize;
int YSize;
int ZSize;
void SetShapeToCube()
{
int sizeCbrt = static_cast<int>(std::cbrt(static_cast<float>(this->Size)));
this->ZSize = sizeCbrt;
this->YSize = sizeCbrt;
this->XSize = this->Size / (this->YSize * this->ZSize);
this->XRank = this->Rank / (this->YSize * this->ZSize);
this->YRank = (this->Rank - (this->XRank * this->YSize * this->ZSize)) / this->ZSize;
this->ZRank =
this->Rank - (this->XRank * this->YSize * this->ZSize) - (this->YRank * this->ZSize);
}
};
struct IterationTimes
{
vtkm::Float64 RenderTime = -1.0f;
vtkm::Float64 CompositeTime = -1.0f;
vtkm::Float64 TotalTime = -1.0f;
};
std::string GetImageName(const std::string& prefix,
const BenchmarkOptions& options,
const MpiTopology& mpiTopology)
{
std::stringstream ss;
ss << prefix << "_" << options.CanvasWidth << "x" << options.CanvasHeight << "_"
<< mpiTopology.Size << ".png";
return ss.str();
}
void CollectTimeStats(vtkm::Float64 time,
vtkm::Float64& minTime,
vtkm::Float64& maxTime,
vtkm::Float64& avgTime)
{
auto comm = vtkm::cont::EnvironmentTracker::GetCommunicator();
std::vector<vtkm::Float64> allTimes(comm.size(), -1.0f);
if (comm.rank() == 0)
vtkmdiy::mpi::gather(comm, time, allTimes, 0);
else
vtkmdiy::mpi::gather(comm, time, 0);
if (comm.rank() == 0)
{
auto minMaxTime = std::minmax_element(allTimes.begin(), allTimes.end());
minTime = *(minMaxTime.first);
maxTime = *(minMaxTime.second);
avgTime = std::accumulate(allTimes.begin(), allTimes.end(), 0.0) /
static_cast<vtkm::Float64>(allTimes.size());
}
}
void SaveTimeStats(const std::vector<IterationTimes>& stats,
const BenchmarkOptions& options,
const MpiTopology& vtkmNotUsed(mpiTopology))
{
auto comm = vtkm::cont::EnvironmentTracker::GetCommunicator();
if (comm.rank() != 0)
return;
std::vector<vtkm::Float64> renderTimes;
std::vector<vtkm::Float64> compositeTimes;
std::vector<vtkm::Float64> totalTimes;
for (const auto& stat : stats)
{
renderTimes.push_back(stat.RenderTime);
compositeTimes.push_back(stat.CompositeTime);
totalTimes.push_back(stat.TotalTime);
}
auto CalculateTimeStats = [](const std::vector<vtkm::Float64>& times,
vtkm::Float64& minTime,
vtkm::Float64& maxTime,
vtkm::Float64& avgTime) {
auto minMaxTime = std::minmax_element(times.begin(), times.end());
minTime = *(minMaxTime.first);
maxTime = *(minMaxTime.second);
avgTime =
std::accumulate(times.begin(), times.end(), 0.0) / static_cast<vtkm::Float64>(times.size());
};
vtkm::Float64 minRTime, maxRTime, avgRTime;
vtkm::Float64 minCTime, maxCTime, avgCTime;
vtkm::Float64 minTTime, maxTTime, avgTTime;
CalculateTimeStats(renderTimes, minRTime, maxRTime, avgRTime);
CalculateTimeStats(compositeTimes, minCTime, maxCTime, avgCTime);
CalculateTimeStats(totalTimes, minTTime, maxTTime, avgTTime);
auto ToHumanReadableMS = [](vtkm::Float64 time) { return static_cast<int>(time * 1000); };
std::ofstream file;
file.open(options.TimingFileName, std::ios::app);
bool hasHeader = file.tellp() != 0;
if (!hasHeader)
{
file << "World Size,Canvas Size,Min Render Time,Max Render Time,Avg Render Time,"
"Min Composite Time,Max Composite Time,Avg Composite Time,"
"Min Total Time,Max Total Time,Avg Total Time"
<< std::endl;
}
file << comm.size() << "," << options.CanvasWidth << "x" << options.CanvasHeight << ",";
file << ToHumanReadableMS(minRTime) << "," << ToHumanReadableMS(maxRTime) << ","
<< ToHumanReadableMS(avgRTime) << ",";
file << ToHumanReadableMS(minCTime) << "," << ToHumanReadableMS(maxCTime) << ","
<< ToHumanReadableMS(avgCTime) << ",";
file << ToHumanReadableMS(minTTime) << "," << ToHumanReadableMS(maxTTime) << ","
<< ToHumanReadableMS(avgTTime) << std::endl;
file.close();
}
void GenerateDataSet(const BenchmarkOptions& options,
const MpiTopology& mpiTopology,
vtkm::cont::DataSet& dataSet,
std::string& fieldName,
vtkm::Range& globalFiendRange,
vtkm::Bounds& globalBounds)
{
fieldName = "perlinnoise";
vtkm::source::PerlinNoise perlin;
perlin.SetCellDimensions(options.PerlinDimensions);
perlin.SetSeed(options.PerlinSeed);
// Perlin Noise does not generate "interesting" surfaces when the scale is too small, .i.e,
// X, Y, Z values are too close to each other. Hence we scale the perlin noise by a factor
vtkm::FloatDefault scale = 3.0f;
vtkm::Vec3f origin = { mpiTopology.XRank * scale,
mpiTopology.YRank * scale,
mpiTopology.ZRank * scale };
vtkm::Vec3f maxExtent = origin + vtkm::Vec3f{ scale, scale, scale };
perlin.SetOrigin(origin);
perlin.SetMaxExtent(maxExtent);
dataSet = perlin.Execute();
vtkm::filter::contour::Contour contour;
contour.SetNumberOfIsoValues(1);
contour.SetIsoValue(0, 0.5f);
contour.SetActiveField(fieldName);
dataSet = contour.Execute(dataSet);
globalFiendRange = { 0.0f, 1.0f };
globalBounds = { vtkm::Vec3f{ 0.0f, 0.0f, 0.0f },
vtkm::Vec3f{ static_cast<vtkm::FloatDefault>(mpiTopology.XSize),
static_cast<vtkm::FloatDefault>(mpiTopology.YSize),
static_cast<vtkm::FloatDefault>(mpiTopology.ZSize) } *
scale };
vtkm::Float64 boundsEps = 0.1f;
globalBounds.Include(globalBounds.MinCorner() -
vtkm::Vec3f_64{ boundsEps, boundsEps, boundsEps });
globalBounds.Include(globalBounds.MaxCorner() +
vtkm::Vec3f_64{ boundsEps, boundsEps, boundsEps });
}
void RunBenchmark(const BenchmarkOptions& options)
{
auto comm = vtkm::cont::EnvironmentTracker::GetCommunicator();
MpiTopology mpiTopology(comm.rank(), comm.size());
mpiTopology.SetShapeToCube();
// Generate DataSet
vtkm::cont::DataSet dataSet;
std::string fieldName;
vtkm::Range globalFiendRange;
vtkm::Bounds globalBounds;
GenerateDataSet(options, mpiTopology, dataSet, fieldName, globalFiendRange, globalBounds);
vtkm::rendering::Scene scene;
vtkm::cont::ColorTable colorTable("inferno");
vtkm::rendering::Actor actor(
dataSet.GetCellSet(), dataSet.GetCoordinateSystem(), dataSet.GetField(fieldName), colorTable);
actor.SetScalarRange(globalFiendRange);
scene.AddActor(actor);
vtkm::rendering::Camera camera;
camera.ResetToBounds(globalBounds);
camera.Azimuth(10.0f);
camera.Elevation(20.0f);
std::vector<IterationTimes> benchmarkTimes;
vtkm::rendering::CanvasRayTracer canvas(options.CanvasWidth, options.CanvasHeight);
for (int iter = 0; iter < options.NumIterations; iter++)
{
vtkm::rendering::Color bg(0.2f, 0.2f, 0.2f, 1.0f);
vtkm::rendering::View3D view(scene, vtkm::rendering::MapperRayTracer(), canvas, camera, bg);
view.Paint();
if (comm.rank() == 0)
{
benchmarkTimes.push_back(
IterationTimes{ .RenderTime = view.GetTimes()[vtkm::rendering::RENDER_TIME_KEY],
.CompositeTime = view.GetTimes()[vtkm::rendering::COMPOSITE_TIME_KEY],
.TotalTime = view.GetTimes()[vtkm::rendering::TOTAL_TIME_KEY] });
}
}
SaveTimeStats(benchmarkTimes, options, mpiTopology);
canvas.SaveAs(GetImageName("perlin_surface", options, mpiTopology));
}
int main(int argc, char* argv[])
{
// Initialize MPI
std::unique_ptr<vtkmdiy::mpi::environment> mpiEnv = nullptr;
if (!vtkmdiy::mpi::environment::initialized())
{
mpiEnv.reset(new vtkmdiy::mpi::environment(argc, argv));
}
// Initialize VTK-m
vtkm::cont::Initialize(argc, argv, vtkm::cont::InitializeOptions::None);
BenchmarkOptions options(argc, argv);
RunBenchmark(options);
return 0;
}

@ -196,6 +196,27 @@ struct DrawColorBar : public vtkm::worklet::WorkletMapField
bool Horizontal;
}; // struct DrawColorBar
struct CopyFromBuffers : public vtkm::worklet::WorkletMapField
{
using ControlSignature = void(FieldInOut, FieldInOut, WholeArrayIn, WholeArrayIn);
using ExecutionSignature = void(_1, _2, _3, _4, WorkIndex);
template <typename ColorPortalType, typename DepthPortalType>
VTKM_EXEC void operator()(vtkm::Vec4f_32& color,
vtkm::Float32& depth,
const ColorPortalType& colorBuffer,
const DepthPortalType& depthBuffer,
const vtkm::Id& index) const
{
vtkm::Id colorOffset = index * 4;
for (vtkm::IdComponent i = 0; i < 4; ++i)
{
color[i] = static_cast<vtkm::Float32>(colorBuffer.Get(colorOffset + i) / 255.0f);
}
depth = static_cast<vtkm::Float32>(depthBuffer.Get(index));
}
}; // struct CopyFromBuffers
} // namespace internal
struct Canvas::CanvasInternals
@ -669,5 +690,13 @@ vtkm::rendering::WorldAnnotator* Canvas::CreateWorldAnnotator() const
{
return new vtkm::rendering::WorldAnnotator(this);
}
void Canvas::CopyFrom(const vtkm::cont::ArrayHandle<unsigned char>& colorBuffer,
const vtkm::cont::ArrayHandle<vtkm::Float32>& depthBuffer)
{
vtkm::worklet::DispatcherMapField<internal::CopyFromBuffers> dispatcher(
internal::CopyFromBuffers{});
dispatcher.Invoke(this->GetColorBuffer(), this->GetDepthBuffer(), colorBuffer, depthBuffer);
}
}
} // vtkm::rendering

@ -200,6 +200,10 @@ public:
VTKM_CONT
void EndTextRenderingBatch() const;
VTKM_CONT
void CopyFrom(const vtkm::cont::ArrayHandle<unsigned char>& colorBuffer,
const vtkm::cont::ArrayHandle<vtkm::Float32>& depthBuffer);
friend class AxisAnnotation2D;
friend class ColorBarAnnotation;
friend class ColorLegendAnnotation;

@ -26,6 +26,9 @@ namespace vtkm
{
namespace rendering
{
const std::string RENDER_TIME_KEY = "RenderTime";
const std::string COMPOSITE_TIME_KEY = "CompositeTime";
const std::string TOTAL_TIME_KEY = "TotalTime";
class VTKM_RENDERING_EXPORT View
{
@ -115,6 +118,9 @@ public:
VTKM_CONT
void AddAdditionalAnnotation(std::function<void(void)> ann);
VTKM_CONT
std::unordered_map<std::string, vtkm::Float64> GetTimes() const { return this->Times; }
protected:
void SetupForWorldSpace(bool viewportClip = true);
@ -125,6 +131,8 @@ protected:
bool WorldAnnotationsEnabled = true;
bool RenderAnnotationsEnabled = true;
std::unordered_map<std::string, vtkm::Float64> Times;
private:
std::shared_ptr<InternalData> Internal;
};

@ -9,6 +9,7 @@
//============================================================================
#include <vtkm/cont/EnvironmentTracker.h>
#include <vtkm/cont/Timer.h>
#include <vtkm/rendering/View3D.h>
#ifdef VTKM_ENABLE_MPI
@ -45,47 +46,40 @@ View3D::~View3D() {}
void View3D::Paint()
{
this->GetCanvas().Clear();
this->RenderAnnotations();
this->GetScene().Render(this->GetMapper(), this->GetCanvas(), this->GetCamera());
this->Times.clear();
vtkm::cont::Timer totalTimer;
vtkm::cont::Timer renderTimer;
totalTimer.Start();
renderTimer.Start();
this->GetCanvas().Clear();
this->GetScene().Render(this->GetMapper(), this->GetCanvas(), this->GetCamera());
renderTimer.Stop();
vtkm::cont::Timer compositeTimer;
compositeTimer.Start();
#ifdef VTKM_ENABLE_MPI
auto comm = vtkm::cont::EnvironmentTracker::GetCommunicator();
if (comm.size() == 1)
return;
this->Compositor.SetCompositeMode(vtkm::rendering::compositing::Compositor::Z_BUFFER_SURFACE);
//volume render
this->Compositor.SetCompositeMode(vtkm::rendering::compositing::Compositor::VIS_ORDER_BLEND);
/*
auto colors = (this->GetCanvas().GetColorBuffer().WritePortal().GetArray())[0][0];
auto depths = (this->GetCanvas().GetDepthBuffer().WritePortal().GetArray());
//auto colors = &GetVTKMPointer(this->GetCanvas().GetColorBuffer())[0][0];
//auto depths = GetVTKMPointer(this->GetCanvas().GetDepthBuffer());
*/
this->Compositor.AddImage(this->GetCanvas());
auto result = this->Compositor.Composite();
//Rank 0 has the composited result, so put it into the Canvas.
if (comm.rank() == 0)
if (comm.size() > 1)
{
this->GetCanvas().Clear();
auto colors = this->GetCanvas().GetColorBuffer();
auto depths = this->GetCanvas().GetDepthBuffer();
int size = this->GetCanvas().GetWidth() * this->GetCanvas().GetHeight();
for (int i = 0; i < size; i++)
this->Compositor.SetCompositeMode(vtkm::rendering::compositing::Compositor::Z_BUFFER_SURFACE);
this->Compositor.AddImage(this->GetCanvas());
auto result = this->Compositor.Composite();
//Rank 0 has the composited result, so put it into the Canvas.
if (comm.rank() == 0)
{
const int offset = i * 4;
vtkm::Vec4f_32 rgba;
for (int j = 0; j < 4; j++)
rgba[j] = static_cast<vtkm::FloatDefault>(result.Pixels[offset + j] / 255.f);
colors.WritePortal().Set(i, rgba);
depths.WritePortal().Set(i, result.Depths[i]);
this->GetCanvas().CopyFrom(vtkm::cont::make_ArrayHandle(result.Pixels, vtkm::CopyFlag::Off),
vtkm::cont::make_ArrayHandle(result.Depths, vtkm::CopyFlag::Off));
}
}
this->RenderAnnotations();
#endif
compositeTimer.Stop();
totalTimer.Stop();
this->Times[RENDER_TIME_KEY] = renderTimer.GetElapsedTime();
this->Times[COMPOSITE_TIME_KEY] = compositeTimer.GetElapsedTime();
this->Times[TOTAL_TIME_KEY] = totalTimer.GetElapsedTime();
}
void View3D::RenderScreenAnnotations()
@ -181,7 +175,7 @@ void View3D::RenderWorldAnnotations()
this->GetWorldAnnotator().BeginLineRenderingBatch();
this->BoxAnnotation.SetColor(Color(.5f, .5f, .5f));
this->BoxAnnotation.SetExtents(this->GetScene().GetSpatialBounds());
this->BoxAnnotation.SetExtents(bounds);
this->BoxAnnotation.Render(this->GetCamera(), this->GetWorldAnnotator());
this->GetWorldAnnotator().EndLineRenderingBatch();

@ -55,7 +55,7 @@ void Compositor::AddImage(vtkm::rendering::Canvas& canvas)
vtkm::Id width = canvas.GetWidth();
vtkm::Id height = canvas.GetHeight();
assert(this->CompositingMode != VIS_ORDER_BLEND);
// assert(this->CompositingMode != VIS_ORDER_BLEND);
assert(depths != NULL);
Image image;
if (this->Images.size() == 0)

@ -61,9 +61,9 @@ struct AddImageBlock
struct AddMultiImageBlock
{
const vtkmdiy::Master& m_master;
std::vector<vtkm::rendering::compositing::Image>& m_images;
vtkm::rendering::compositing::Image& m_output;
const vtkmdiy::Master& m_master;
AddMultiImageBlock(vtkmdiy::Master& master,
std::vector<vtkm::rendering::compositing::Image>& images,

@ -212,8 +212,8 @@ vtkm::cont::DataSet PerlinNoise::DoExecute() const
vtkm::cont::DataSet dataSet;
const vtkm::Vec3f cellDims = this->GetCellDimensions();
const vtkm::Vec3f spacing(1.0f / cellDims[0], 1.0f / cellDims[1], 1.0f / cellDims[2]);
const vtkm::Vec3f extentDelta = this->MaxExtent - this->Origin;
const vtkm::Vec3f spacing = extentDelta / cellDims;
vtkm::cont::CellSetStructured<3> cellSet;
cellSet.SetPointDimensions(this->PointDimensions);

@ -55,6 +55,9 @@ public:
VTKM_CONT vtkm::Vec3f GetOrigin() const { return this->Origin; }
VTKM_CONT void SetOrigin(const vtkm::Vec3f& origin) { this->Origin = origin; }
VTKM_CONT vtkm::Vec3f GetMaxExtent() const { return this->MaxExtent; }
VTKM_CONT void SetMaxExtent(const vtkm::Vec3f& maxExtent) { this->MaxExtent = maxExtent; }
/// \brief The seed used for the pseudorandom number generation of the noise.
///
/// If the seed is not set, then a new, unique seed is picked each time `Execute` is run.
@ -70,6 +73,7 @@ private:
vtkm::Id3 PointDimensions = { 16, 16, 16 };
vtkm::Vec3f Origin = { 0, 0, 0 };
vtkm::Vec3f MaxExtent = { 1, 1, 1 };
vtkm::IdComponent Seed = 0;
bool SeedSet = false;
};