diff --git a/README b/README new file mode 100644 index 000000000..6cfb6066f --- /dev/null +++ b/README @@ -0,0 +1,142 @@ +vtkh: +tests/vtkh/vtk-h_render.cpp + +TEST(vtkh_render, vtkh_bg_color) + + +make data +bounds +camera setup. +vtkh::MakeRender(...) + vtkh::Render: info needed to create a single image. N domains = N canvases + camera, bounds, FG/BG colors + +vtkh::RayTracer tracer; +tracer.SetInput(...); + +vtkh::Scene scene; +scene.AddRender(render); +scene.AddRenderer(&tracer); +scene.Render(); + +vtkh::RayTracer : public vtkh::Renderer + --it returns a vtkm::rendering::CanvasRayTracer(width,height) + DoExecute() does the rendering + PostExecute() does the compositing. + + +vtkh::Scene + list of vtkh::Renderer (raytracer, ...) + vector of vtkh::Render (camera, data, fg/bg) + Scene::Render(): + for each renderer: + renderer->Update() does the rendering. + (do opaque, then volume) + RenderWorldAnnotations() + Render::RenderWorldAnnotations() + if rank != 0 then RETURN + Annotator annotator(canvas, camera, m_scene_bounds) + annotator.RenderWorldAnnotations() + RenderScreenAnnotations() + +vtkh::Image. pixels, depth, composite order, ... + + +vtkh::Renderer : public Filter + DoExecute: + for each ds in m_input + m_mapper->RenderCells(ds) + this->Composite(...) + + +vtkh::Compositor + Composite() + calls RadixKCompositor, etc. + + + +vtkh::Render + camera + image name + width/height/bounds + fg/bg colors + +vtkh::RayTracer + SetInput: dataset + SetField: scalar + +vtkh::Scene + AddRender: vtkh::Render + AddRenderer: vtkh::RayTracer + + + +vtkh::Scene.Render(); + + + +======================================================================================= +vtkm: +vtkm::rendering::Scene + AddActor: dataset + Render(mapper, canvas, camera) + +vtkm::rendering::View + camera + canvas + mapper + scene + annotations + Paint() does the rendering + +vtkm::rendering::Canvas + Fg/Bg + color/depth buffers + model/view mtx + + +============================================ +VTK-m: simpler example... ? +examples/demo/Demo.cxx + +vtkm::rendering::Camera (look at, up, clip, etc). +vtkm::rendering::Actor (dataset) +vtkm::rendering::CanvasRayTracer (x,y) + +vtkm::rendering::Scene (actor) +vtkm::rendering::View3D view (scene, mapper, canvas, camera, bg) + +view.Paint() (renders the image). + + + + + +========================================================================================= +Comparing classes, etc. +Camera: the same. vtkm::rendering::Camera + +Scene: +vtkm: vector of vtkm::rendering::Actor +vtkh: list/vector of vtkh::Renderer, batchsize, has_volume. + +vtkh::Render aprox equal to vtkm::rendering::View + + + + + + +============================================================================== +vtkh volume render unstructured: + + +scene.AddRender() +scene.AddRenderer(vtkh::VolumeRenderer); +scene.Render() + // pass 2 for volume + renderer->SetComponosite(true) + renderer->SetRenders(current_batch); + renderer->Update() //does the rendering... + DoExecute() + vtkm::rendering::Mapper::RenderCells() diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 54e4e376a..1344dd610 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -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) diff --git a/examples/demo/Demo.cxx b/examples/demo/Demo.cxx index 35e099016..e9b26dfd5 100644 --- a/examples/demo/Demo.cxx +++ b/examples/demo/Demo.cxx @@ -8,6 +8,8 @@ // PURPOSE. See the above copyright notice for more information. //============================================================================ +#include + #include #include @@ -39,6 +41,9 @@ int main(int argc, char* argv[]) vtkm::cont::DataSet tangleData = tangle.Execute(); std::string fieldName = "tangle"; + vtkm::io::VTKDataSetWriter writer("tangle.vtk"); + writer.WriteDataSet(tangleData); + // Set up a camera for rendering the input data vtkm::rendering::Camera camera; camera.SetLookAt(vtkm::Vec3f_32(0.5, 0.5, 0.5)); @@ -57,7 +62,7 @@ int main(int argc, char* argv[]) vtkm::rendering::Scene scene; scene.AddActor(actor); // 2048x2048 pixels in the canvas: - CanvasRayTracer canvas(2048, 2048); + CanvasRayTracer canvas(512, 512); // Create a view and use it to render the input data using OS Mesa vtkm::rendering::View3D view(scene, MapperVolume(), canvas, camera, bg); diff --git a/examples/frontier_benchmark/CMakeLists.txt b/examples/frontier_benchmark/CMakeLists.txt new file mode 100644 index 000000000..98810dc89 --- /dev/null +++ b/examples/frontier_benchmark/CMakeLists.txt @@ -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() diff --git a/examples/frontier_benchmark/FrontierBenchmark.cxx b/examples/frontier_benchmark/FrontierBenchmark.cxx new file mode 100644 index 000000000..3392ecbfa --- /dev/null +++ b/examples/frontier_benchmark/FrontierBenchmark.cxx @@ -0,0 +1,457 @@ +//============================================================================ +// 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +const static std::string PERLIN_MODE_GROW = "grow"; +const static std::string PERLIN_MODE_SUBDIVIDE = "subdivide"; +const static std::string CAMERA_MODE_STATIC = "static"; +const static std::string CAMERA_MODE_ORBIT = "orbit"; + +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) + { + seperatorPos = arg.size(); + } + + std::string key = arg.substr(0, seperatorPos); + std::string val = ""; + if (seperatorPos != std::string::npos && seperatorPos + 1 < arg.size()) + { + 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 == "--perlin-mode") + { + this->PerlinMode = val; + } + else if (key == "--perlin-scale") + { + this->PerlinScale = std::stof(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; + } + else if (key == "--camera-mode") + { + this->CameraMode = val; + } + else if (key == "--image-format") + { + this->ImageFormat = val; + } + else if (key == "--show-args") + { + this->ShowArgs = true; + } + } + } + vtkm::Id3 PerlinDimensions = vtkm::Id3(1024, 1024, 1024); + vtkm::IdComponent PerlinSeed = 1; + std::string PerlinMode = PERLIN_MODE_GROW; + vtkm::Float32 PerlinScale = 3.0f; + vtkm::Id CanvasWidth = 1920; + vtkm::Id CanvasHeight = 1080; + vtkm::Id NumIterations = 10; + std::string TimingFileName = "timing.csv"; + std::string ImageFormat = "png"; + std::string CameraMode = CAMERA_MODE_STATIC; + bool ShowArgs = false; +}; + +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(std::cbrt(static_cast(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 << options.CameraMode << "/" << prefix << "_" << options.PerlinMode << "_" + << options.CanvasWidth << "x" << options.CanvasHeight << "_" << mpiTopology.Size << "." + << options.ImageFormat; + return ss.str(); +} + +std::string GetFrameName(const std::string& prefix, + int frameNumber, + const BenchmarkOptions& options, + const MpiTopology& mpiTopology) +{ + std::stringstream ss; + ss << options.CameraMode << "/" << prefix << "_" << options.PerlinMode << "_" << mpiTopology.Size + << "_" << options.CameraMode << "_frame_" << std::setw(4) << std::setfill('0') << frameNumber + << "." << options.ImageFormat; + 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 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(allTimes.size()); + } +} + +void SaveTimeStats(const std::vector& stats, + const BenchmarkOptions& options, + const MpiTopology& vtkmNotUsed(mpiTopology)) +{ + auto comm = vtkm::cont::EnvironmentTracker::GetCommunicator(); + if (comm.rank() != 0) + return; + + std::vector renderTimes; + std::vector compositeTimes; + std::vector 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& 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(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(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 + if (options.PerlinMode == PERLIN_MODE_GROW) + { + vtkm::Vec3f origin = vtkm::Vec3f_32{ static_cast(mpiTopology.XRank), + static_cast(mpiTopology.YRank), + static_cast(mpiTopology.ZRank) } * + options.PerlinScale; + vtkm::Vec3f maxExtent = origin + vtkm::Vec3f{ options.PerlinScale }; + perlin.SetOrigin(origin); + perlin.SetMaxExtent(maxExtent); + dataSet = perlin.Execute(); + } + else if (options.PerlinMode == PERLIN_MODE_SUBDIVIDE) + { + vtkm::Vec3f_32 blockSpacing = vtkm::Vec3f_32{ options.PerlinScale } * + vtkm::Vec3f_32{ 1.0f / static_cast(mpiTopology.XSize), + 1.0f / static_cast(mpiTopology.YSize), + 1.0f / static_cast(mpiTopology.ZSize) }; + vtkm::Vec3f_32 origin = vtkm::Vec3f_32{ static_cast(mpiTopology.XRank), + static_cast(mpiTopology.YRank), + static_cast(mpiTopology.ZRank) } * + blockSpacing; + vtkm::Vec3f_32 maxExtent = origin + blockSpacing; + + perlin.SetOrigin(origin); + perlin.SetMaxExtent(maxExtent); + dataSet = perlin.Execute(); + } + + std::vector isoValues{ 0.4f, 0.75f }; + vtkm::filter::contour::Contour contour; + contour.SetIsoValues(isoValues); + contour.SetActiveField(fieldName); + dataSet = contour.Execute(dataSet); + + globalFiendRange = { 0.0f, 1.0f }; + if (options.PerlinMode == PERLIN_MODE_GROW) + { + globalBounds = { vtkm::Vec3f{ 0.0f, 0.0f, 0.0f }, + vtkm::Vec3f{ static_cast(mpiTopology.XSize), + static_cast(mpiTopology.YSize), + static_cast(mpiTopology.ZSize) } * + options.PerlinScale }; + } + else if (options.PerlinMode == PERLIN_MODE_SUBDIVIDE) + { + globalBounds = { vtkm::Vec3f{ 0.0f }, vtkm::Vec3f{ options.PerlinScale } }; + } + + // Add a small epsilon to the bounds to prevent the world annotations from being clipped + /* + vtkm::Float64 boundsEps = 0.0f; + if (options.CameraMode == CAMERA_MODE_ORBIT) + { + boundsEps = 0.2f; + } + 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); + + vtkm::rendering::CanvasRayTracer canvas(options.CanvasWidth, options.CanvasHeight); + + if (options.CameraMode == CAMERA_MODE_STATIC) + { + camera.Azimuth(10.0f); + camera.Elevation(20.0f); + std::vector benchmarkTimes; + 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); + if (mpiTopology.Rank == 0) + { + canvas.SaveAs(GetImageName("perlin_static", options, mpiTopology)); + } + } + else if (options.CameraMode == CAMERA_MODE_ORBIT) + { + std::random_device dev; + std::mt19937 rng(dev()); + std::uniform_real_distribution dist(0.0, 1.0); + vtkm::Float64 dirX = -1.0f; + 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 (mpiTopology.Rank == 0) + { + canvas.SaveAs(GetFrameName("perlin_movie", iter, options, mpiTopology)); + } + + vtkm::Float64 speedX = 0.01f * dirX; + vtkm::Float64 speedY = 0.0f; + camera.TrackballRotate(0.0, 0.0, speedX, speedY); + + if (mpiTopology.Rank == 0 && iter > 0 && (iter + 1) % 10 == 0) + { + std::cerr << "Frame " << (iter + 1) << " of " << options.NumIterations << " done" + << std::endl; + } + } + } +} + +int main(int argc, char* argv[]) +{ + // Initialize MPI + std::unique_ptr 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); + if (options.ShowArgs) + { + std::cerr << std::boolalpha; + std::cerr << argv[0] << ":" << std::endl; + std::cerr << "\tPerlin Dimensions: " << options.PerlinDimensions << std::endl; + std::cerr << "\tPerlin Seed: " << options.PerlinSeed << std::endl; + std::cerr << "\tCanvas Width: " << options.CanvasWidth << std::endl; + std::cerr << "\tCanvas Height: " << options.CanvasHeight << std::endl; + std::cerr << "\tNum Iterations: " << options.NumIterations << std::endl; + std::cerr << "\tTiming File: " << options.TimingFileName << std::endl; + std::cerr << "\tCamera Mode: " << options.CameraMode << std::endl; + std::cerr << "\tShow Args: " << options.ShowArgs << std::endl; + std::cerr << std::noboolalpha; + } + + RunBenchmark(options); + return 0; +} \ No newline at end of file diff --git a/vtkm/rendering/CMakeLists.txt b/vtkm/rendering/CMakeLists.txt index e19985206..eb6ccebb8 100644 --- a/vtkm/rendering/CMakeLists.txt +++ b/vtkm/rendering/CMakeLists.txt @@ -55,6 +55,10 @@ set(headers View3D.h Wireframer.h WorldAnnotator.h + + compositing/Compositor.h + compositing/Image.h + compositing/PNGEncoder.h ) set(sources @@ -86,6 +90,15 @@ set(sources raytracing/Logger.cxx raytracing/MeshConnectivityContainers.cxx raytracing/TriangleExtractor.cxx + + compositing/Compositor.cxx + compositing/DirectSendCompositor.cxx + compositing/Image.cxx + compositing/PartialCompositor.cxx + compositing/PNGEncoder.cxx + compositing/RadixKCompositor.cxx + compositing/PayloadCompositor.cxx + compositing/PayloadImage.cxx ) # This list of sources has code that uses devices and so might need to be @@ -147,6 +160,10 @@ if(UNIX AND NOT APPLE) target_link_libraries(vtkm_rendering PRIVATE rt) endif() +if (VTKm_ENABLE_MPI) + target_link_libraries(vtkm_rendering PUBLIC MPI::MPI_CXX) +endif() + #----------------------------------------------------------------------------- add_subdirectory(internal) add_subdirectory(raytracing) diff --git a/vtkm/rendering/Canvas.cxx b/vtkm/rendering/Canvas.cxx index 921c6590d..b76cae922 100644 --- a/vtkm/rendering/Canvas.cxx +++ b/vtkm/rendering/Canvas.cxx @@ -12,6 +12,7 @@ #include #include +#include #include #include #include @@ -195,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 + 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(colorBuffer.Get(colorOffset + i) / 255.0f); + } + depth = static_cast(depthBuffer.Get(index)); + } +}; // struct CopyFromBuffers + } // namespace internal struct Canvas::CanvasInternals @@ -615,6 +637,13 @@ void Canvas::SetViewToScreenSpace(const vtkm::rendering::Camera& vtkmNotUsed(cam void Canvas::SaveAs(const std::string& fileName) const { + //Only rank 0 has the composited image. +#ifdef VTKM_ENABLE_MPI + auto comm = vtkm::cont::EnvironmentTracker::GetCommunicator(); + if (comm.rank() != 0) + return; +#endif + this->RefreshColorBuffer(); ColorBufferType::ReadPortalType colorPortal = GetColorBuffer().ReadPortal(); vtkm::Id width = GetWidth(); @@ -661,5 +690,13 @@ vtkm::rendering::WorldAnnotator* Canvas::CreateWorldAnnotator() const { return new vtkm::rendering::WorldAnnotator(this); } + +void Canvas::CopyFrom(const vtkm::cont::ArrayHandle& colorBuffer, + const vtkm::cont::ArrayHandle& depthBuffer) +{ + vtkm::worklet::DispatcherMapField dispatcher( + internal::CopyFromBuffers{}); + dispatcher.Invoke(this->GetColorBuffer(), this->GetDepthBuffer(), colorBuffer, depthBuffer); +} } } // vtkm::rendering diff --git a/vtkm/rendering/Canvas.h b/vtkm/rendering/Canvas.h index b58a9639a..20072474a 100644 --- a/vtkm/rendering/Canvas.h +++ b/vtkm/rendering/Canvas.h @@ -225,6 +225,10 @@ public: VTKM_CONT void EndTextRenderingBatch() const; + VTKM_CONT + void CopyFrom(const vtkm::cont::ArrayHandle& colorBuffer, + const vtkm::cont::ArrayHandle& depthBuffer); + friend class AxisAnnotation2D; friend class ColorBarAnnotation; friend class ColorLegendAnnotation; diff --git a/vtkm/rendering/MapperRayTracer.cxx b/vtkm/rendering/MapperRayTracer.cxx index 90b98fd8e..d842c54aa 100644 --- a/vtkm/rendering/MapperRayTracer.cxx +++ b/vtkm/rendering/MapperRayTracer.cxx @@ -153,6 +153,11 @@ void MapperRayTracer::SetShadingOn(bool on) this->Internals->Shade = on; } +void MapperRayTracer::SetLightPosition(const vtkm::Vec3f_32& lightPosition) +{ + // this->Internals->Tracer.SetLightPosition(lightPosition); +} + vtkm::rendering::Mapper* MapperRayTracer::NewCopy() const { return new vtkm::rendering::MapperRayTracer(*this); diff --git a/vtkm/rendering/MapperRayTracer.h b/vtkm/rendering/MapperRayTracer.h index fa32d7c96..03d06a3f3 100644 --- a/vtkm/rendering/MapperRayTracer.h +++ b/vtkm/rendering/MapperRayTracer.h @@ -39,6 +39,8 @@ public: vtkm::rendering::Mapper* NewCopy() const override; void SetShadingOn(bool on); + void SetLightPosition(const vtkm::Vec3f_32& lightPosition); + private: struct InternalsType; std::shared_ptr Internals; diff --git a/vtkm/rendering/View.cxx b/vtkm/rendering/View.cxx index dfff42ca0..2dbf8d75c 100644 --- a/vtkm/rendering/View.cxx +++ b/vtkm/rendering/View.cxx @@ -183,9 +183,6 @@ void View::RenderAnnotations() { if (this->RenderAnnotationsEnabled) { - this->SetupForScreenSpace(); - this->RenderScreenAnnotations(); - this->GetCanvas().BeginTextRenderingBatch(); for (auto& textAnnotation : this->Internal->TextAnnotations) { @@ -203,6 +200,9 @@ void View::RenderAnnotations() { this->RenderWorldAnnotations(); } + + this->SetupForScreenSpace(); + this->RenderScreenAnnotations(); } } diff --git a/vtkm/rendering/View.h b/vtkm/rendering/View.h index a880e4f17..85041b931 100644 --- a/vtkm/rendering/View.h +++ b/vtkm/rendering/View.h @@ -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"; /// @brief The abstract class representing the view of a rendering scene. class VTKM_RENDERING_EXPORT View @@ -133,6 +136,9 @@ public: VTKM_CONT void AddAdditionalAnnotation(std::function ann); + VTKM_CONT + std::unordered_map GetTimes() const { return this->Times; } + protected: void SetupForWorldSpace(bool viewportClip = true); @@ -143,6 +149,8 @@ protected: bool WorldAnnotationsEnabled = true; bool RenderAnnotationsEnabled = true; + std::unordered_map Times; + private: std::unique_ptr Internal; }; diff --git a/vtkm/rendering/View3D.cxx b/vtkm/rendering/View3D.cxx index 78322f6b1..75935bdca 100644 --- a/vtkm/rendering/View3D.cxx +++ b/vtkm/rendering/View3D.cxx @@ -8,8 +8,16 @@ // PURPOSE. See the above copyright notice for more information. //============================================================================ +#include +#include #include +#ifdef VTKM_ENABLE_MPI +#include +#include +#include +#endif + namespace vtkm { namespace rendering @@ -36,20 +44,84 @@ View3D::View3D(const vtkm::rendering::Scene& scene, void View3D::Paint() { + this->Times.clear(); + + vtkm::cont::Timer totalTimer; + vtkm::cont::Timer renderTimer; + totalTimer.Start(); + renderTimer.Start(); this->GetCanvas().Clear(); - this->RenderAnnotations(); 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) + { + 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) + { + 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() { - if (this->GetScene().GetNumberOfActors() > 0) + vtkm::Range scalarRange; + + int numActors = this->GetScene().GetNumberOfActors(); + if (numActors > 0) + scalarRange = this->GetScene().GetActor(0).GetScalarRange(); + + int totNumActors = numActors; + + /* +#ifdef VTKM_ENABLE_MPI + auto comm = vtkm::cont::EnvironmentTracker::GetCommunicator(); + + vtkm::Float64 minVal = scalarRange.Min, maxVal = scalarRange.Max; + + MPI_Comm mpiComm = vtkmdiy::mpi::mpi_cast(comm.handle()); + int totNumActors = 0; + vtkm::Float64 minVal_res = 0, maxVal_res = 0; + MPI_Reduce(&numActors, &totNumActors, 1, MPI_INT, MPI_SUM, 0, mpiComm); + MPI_Reduce(&minVal, &minVal_res, 1, MPI_DOUBLE, MPI_MIN, 0, mpiComm); + MPI_Reduce(&maxVal, &maxVal_res, 1, MPI_DOUBLE, MPI_MAX, 0, mpiComm); + if (comm.rank() != 0) + return; + + scalarRange.Min = minVal_res; + scalarRange.Max = maxVal_res; +#endif + + std::cout<<"totNumActors= "<CompositingMode == Z_BUFFER_SURFACE) + { + // + // Do local composite and keep a single image + // + image.Init(colors, depths, width, height); + vtkm::rendering::compositing::ImageCompositor compositor; + compositor.ZBufferComposite(this->Images[0], image); + } + else + { + const size_t image_index = this->Images.size(); + this->Images.push_back(image); + this->Images[image_index].Init(colors, depths, width, height); + } +} + + +/* +void Compositor::AddImage(const vtkm::cont::ArrayHandle>& colors, + const vtkm::cont::ArrayHandle& depths, + vtkm::Id width, + vtkm::Id height) +{ + auto c = colors.WritePortal().GetArray(); + auto d = depths.WritePortal().GetArray(); + this->AddImage(c, d, width, height); +} + +void Compositor::AddImage(const unsigned char* color_buffer, + const float* depth_buffer, + const int width, + const int height) +{ + assert(this->CompositingMode != VIS_ORDER_BLEND); + assert(depth_buffer != NULL); + Image image; + if (this->Images.size() == 0) + { + this->Images.push_back(image); + this->Images[0].Init(color_buffer, depth_buffer, width, height); + //this->Images[0].Save("first.png"); + } + else if (this->CompositingMode == Z_BUFFER_SURFACE) + { + // + // Do local composite and keep a single image + // + image.Init(color_buffer, depth_buffer, width, height); + vtkm::rendering::compositing::ImageCompositor compositor; + compositor.ZBufferComposite(this->Images[0], image); + } + else + { + const size_t image_index = this->Images.size(); + this->Images.push_back(image); + this->Images[image_index].Init(color_buffer, depth_buffer, width, height); + } +} + +void Compositor::AddImage(const float* color_buffer, + const float* depth_buffer, + const int width, + const int height) +{ + assert(this->CompositingMode != VIS_ORDER_BLEND); + assert(depth_buffer != NULL); + Image image; + if (this->Images.size() == 0) + { + this->Images.push_back(image); + this->Images[0].Init(color_buffer, depth_buffer, width, height); + } + else if (this->CompositingMode == Z_BUFFER_SURFACE) + { + // + // Do local composite and keep a single image + // + image.Init(color_buffer, depth_buffer, width, height); + + vtkm::rendering::compositing::ImageCompositor compositor; + compositor.ZBufferComposite(this->Images[0], image); + } + else + { + const size_t image_index = this->Images.size(); + this->Images.push_back(image); + this->Images[image_index].Init(color_buffer, depth_buffer, width, height); + } +} + +void Compositor::AddImage(const unsigned char* color_buffer, + const float* depth_buffer, + const int width, + const int height, + const int vis_order) +{ + assert(this->CompositingMode == VIS_ORDER_BLEND); + Image image; + const size_t image_index = this->Images.size(); + this->Images.push_back(image); + this->Images[image_index].Init(color_buffer, depth_buffer, width, height, vis_order); +} + + +void Compositor::AddImage(const float* color_buffer, + const float* depth_buffer, + const int width, + const int height, + const int vis_order) +{ + assert(this->CompositingMode == VIS_ORDER_BLEND); + Image image; + const size_t image_index = this->Images.size(); + this->Images.push_back(image); + + this->Images[image_index].Init(color_buffer, depth_buffer, width, height, vis_order); +} +*/ + +Image Compositor::Composite() +{ + assert(this->Images.size() != 0); + + if (this->CompositingMode == Z_BUFFER_SURFACE) + { + CompositeZBufferSurface(); + } + else if (this->CompositingMode == Z_BUFFER_BLEND) + { + CompositeZBufferBlend(); + } + else if (this->CompositingMode == VIS_ORDER_BLEND) + { + CompositeVisOrder(); + } + // Make this a param to avoid the copy? + return this->Images[0]; +} + +void Compositor::Cleanup() {} + +std::string Compositor::GetLogString() +{ + std::string res = m_log_stream.str(); + m_log_stream.str(""); + return res; +} + +void Compositor::CompositeZBufferSurface() +{ + // nothing to do here in serial. Images were composited as + // they were added to the compositor +#ifdef VTKM_ENABLE_MPI + auto comm = vtkm::cont::EnvironmentTracker::GetCommunicator(); + + assert(this->Images.size() == 1); + RadixKCompositor compositor; + compositor.CompositeSurface(comm, this->Images[0]); + m_log_stream << compositor.GetTimingString(); +#endif +} + +void Compositor::CompositeZBufferBlend() +{ + throw vtkm::cont::ErrorBadValue("Not implemented"); +} + +void Compositor::CompositeVisOrder() +{ + +#ifdef VTKM_ENABLE_MPI + auto comm = vtkm::cont::EnvironmentTracker::GetCommunicator(); + assert(this->Images.size() != 0); + vtkm::rendering::compositing::DirectSendCompositor compositor; + compositor.CompositeVolume(comm, this->Images); +#else + vtkm::rendering::compositing::ImageCompositor compositor; + compositor.OrderedComposite(this->Images); +#endif +} + +} +} +} //namespace vtkm::rendering::compositing diff --git a/vtkm/rendering/compositing/Compositor.h b/vtkm/rendering/compositing/Compositor.h new file mode 100644 index 000000000..8ce85e853 --- /dev/null +++ b/vtkm/rendering/compositing/Compositor.h @@ -0,0 +1,109 @@ +//============================================================================ +// 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_rendering_compositing_Compositor_h +#define vtk_m_rendering_compositing_Compositor_h + +#include + +#include +#include + +#ifdef VTKM_ENABLE_MPI +#include +#endif + +namespace vtkm +{ +namespace rendering +{ +namespace compositing +{ + +class VTKM_RENDERING_EXPORT Compositor +{ +public: + enum CompositeMode + { + Z_BUFFER_SURFACE, // zbuffer composite no transparency + Z_BUFFER_BLEND, // zbuffer composite with transparency + VIS_ORDER_BLEND // blend images in a specific order + }; + Compositor(); + + virtual ~Compositor(); + + void SetCompositeMode(CompositeMode composite_mode); + + void ClearImages(); + + void AddImage(vtkm::rendering::Canvas& canvas); + + /* + void AddImage(const unsigned char* color_buffer, + const float* depth_buffer, + const int width, + const int height); + + void AddImage(const float* color_buffer, + const float* depth_buffer, + const int width, + const int height); + + void AddImage(const unsigned char* color_buffer, + const float* depth_buffer, + const int width, + const int height, + const int vis_order); + + void AddImage(const float* color_buffer, + const float* depth_buffer, + const int width, + const int height, + const int vis_order); +*/ + + Image Composite(); + + virtual void Cleanup(); + + std::string GetLogString(); + + unsigned char* ConvertBuffer(const float* buffer, const int size) + { + unsigned char* ubytes = new unsigned char[size]; + +#ifdef VTKH_OPENMP_ENABLED +#pragma omp parallel for +#endif + for (int i = 0; i < size; ++i) + { + ubytes[i] = static_cast(buffer[i] * 255.f); + } + + return ubytes; + } + +protected: + virtual void CompositeZBufferSurface(); + virtual void CompositeZBufferBlend(); + virtual void CompositeVisOrder(); + + std::stringstream m_log_stream; + CompositeMode CompositingMode; + std::vector Images; +}; + +} +} +} //namespace vtkm::rendering::compositing + + +#endif //vtk_m_rendering_compositing_Compositor_h diff --git a/vtkm/rendering/compositing/DirectSendCompositor.cxx b/vtkm/rendering/compositing/DirectSendCompositor.cxx new file mode 100644 index 000000000..1a6605594 --- /dev/null +++ b/vtkm/rendering/compositing/DirectSendCompositor.cxx @@ -0,0 +1,203 @@ +//============================================================================ +// 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 +*/ + +namespace vtkm +{ +namespace rendering +{ +namespace compositing +{ + +namespace internal +{ +struct Redistribute +{ + typedef vtkmdiy::RegularDecomposer Decomposer; + const vtkmdiy::RegularDecomposer& m_decomposer; + Redistribute(const Decomposer& decomposer) + : m_decomposer(decomposer) + { + } + + void operator()(void* v_block, const vtkmdiy::ReduceProxy& proxy) const + { + MultiImageBlock* block = static_cast(v_block); + // + // first round we have no incoming. Take the image we have, + // chop it up into pieces, and send it to the domain resposible + // for that portion + // + const int world_size = m_decomposer.nblocks; + const int local_images = block->m_images.size(); + if (proxy.in_link().size() == 0) + { + std::map> outgoing; + + for (int i = 0; i < world_size; ++i) + { + vtkmdiy::DiscreteBounds sub_image_bounds(3); + m_decomposer.fill_bounds(sub_image_bounds, i); + vtkm::Bounds vtkm_sub_bounds = + vtkm::rendering::compositing::DIYBoundsToVTKM(sub_image_bounds); + + vtkmdiy::BlockID dest = proxy.out_link().target(i); + outgoing[dest].resize(local_images); + + for (int img = 0; img < local_images; ++img) + { + outgoing[dest][img].SubsetFrom(block->m_images[img], vtkm_sub_bounds); + } + } //for + + typename std::map>::iterator it; + for (it = outgoing.begin(); it != outgoing.end(); ++it) + { + proxy.enqueue(it->first, it->second); + } + } // if + else if (block->m_images.at(0).CompositeOrder != -1) + { + // blend images according to vis order + std::vector images; + for (int i = 0; i < proxy.in_link().size(); ++i) + { + + std::vector incoming; + int gid = proxy.in_link().target(i).gid; + proxy.dequeue(gid, incoming); + const int in_size = incoming.size(); + for (int img = 0; img < in_size; ++img) + { + images.emplace_back(incoming[img]); + //std::cout<<"rank "<m_output.Swap(images[0]); + } // else if + else if (block->m_images.at(0).CompositeOrder == -1 && + block->m_images.at(0).GetHasTransparency()) + { + std::vector images; + for (int i = 0; i < proxy.in_link().size(); ++i) + { + + std::vector incoming; + int gid = proxy.in_link().target(i).gid; + proxy.dequeue(gid, incoming); + const int in_size = incoming.size(); + for (int img = 0; img < in_size; ++img) + { + images.emplace_back(incoming[img]); + //std::cout<<"rank "<& images) +{ + vtkmdiy::DiscreteBounds global_bounds = + vtkm::rendering::compositing::VTKMBoundsToDIY(images.at(0).OrigBounds); + + const int num_threads = 1; + const int num_blocks = diy_comm.size(); + const int magic_k = 8; + Image sub_image; + // + // DIY does not seem to like being called with different block types + // so we isolate them within separate blocks + // + { + vtkmdiy::Master master(diy_comm, num_threads, -1, 0, [](void* b) { + ImageBlock* block = reinterpret_cast*>(b); + delete block; + }); + + // create an assigner with one block per rank + vtkmdiy::ContiguousAssigner assigner(num_blocks, num_blocks); + + AddMultiImageBlock create(master, images, sub_image); + + const int dims = 2; + vtkmdiy::RegularDecomposer decomposer(dims, global_bounds, num_blocks); + decomposer.decompose(diy_comm.rank(), assigner, create); + + vtkmdiy::all_to_all(master, assigner, internal::Redistribute(decomposer), magic_k); + } + + { + vtkmdiy::Master master(diy_comm, num_threads, -1, 0, [](void* b) { + ImageBlock* block = reinterpret_cast*>(b); + delete block; + }); + vtkmdiy::ContiguousAssigner assigner(num_blocks, num_blocks); + + const int dims = 2; + vtkmdiy::RegularDecomposer decomposer(dims, global_bounds, num_blocks); + AddImageBlock all_create(master, sub_image); + decomposer.decompose(diy_comm.rank(), assigner, all_create); + diy_comm.barrier(); + //MPI_Barrier(diy_comm); + + //MPICollect(sub_image,diy_comm); + vtkmdiy::all_to_all(master, assigner, CollectImages(decomposer), magic_k); + } + + images.at(0).Swap(sub_image); +} + +std::string DirectSendCompositor::GetTimingString() +{ + std::string res(m_timing_log.str()); + m_timing_log.str(""); + return res; +} + +} +} +} //namespace vtkm::rendering::compositing diff --git a/vtkm/rendering/compositing/DirectSendCompositor.h b/vtkm/rendering/compositing/DirectSendCompositor.h new file mode 100644 index 000000000..dac0cad1c --- /dev/null +++ b/vtkm/rendering/compositing/DirectSendCompositor.h @@ -0,0 +1,44 @@ +//============================================================================ +// 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_rendering_compositing_DirectSendCompositor_h +#define vtk_m_rendering_compositing_DirectSendCompositor_h + +#include + +#include + +#include + +namespace vtkm +{ +namespace rendering +{ +namespace compositing +{ + +class VTKM_RENDERING_EXPORT DirectSendCompositor +{ +public: + DirectSendCompositor(); + ~DirectSendCompositor(); + void CompositeVolume(vtkmdiy::mpi::communicator& diy_comm, + std::vector& images); + std::string GetTimingString(); + +private: + std::stringstream m_timing_log; +}; + +} +} +} //namespace vtkm::rendering::compositing + +#endif //vtk_m_rendering_compositing_DirectSendCompositor_h diff --git a/vtkm/rendering/compositing/EmissionPartial.h b/vtkm/rendering/compositing/EmissionPartial.h new file mode 100644 index 000000000..b739aadf9 --- /dev/null +++ b/vtkm/rendering/compositing/EmissionPartial.h @@ -0,0 +1,117 @@ +//============================================================================ +// 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 vtkm_rendering_comositing_emmsion_partial_h +#define vtkm_rendering_comositing_emmsion_partial_h + +#include +#include +#include + +namespace vtkm +{ +namespace rendering +{ +namespace compositing +{ + +template +struct EmissionPartial +{ + typedef FloatType ValueType; + + int m_pixel_id; + double m_depth; + std::vector m_bins; + std::vector m_emission_bins; + + EmissionPartial() + : m_pixel_id(0) + , m_depth(0.f) + { + } + + void alter_bin(int bin, FloatType value) + { + m_bins[bin] = value; + m_emission_bins[bin] = value; + } + + void print() + { + std::cout << "Partial id " << m_pixel_id << "\n"; + std::cout << "Absorption : "; + for (int i = 0; i < m_bins.size(); ++i) + { + std::cout << m_bins[i] << " "; + } + std::cout << "\n"; + std::cout << "Emission: "; + for (int i = 0; i < m_bins.size(); ++i) + { + std::cout << m_emission_bins[i] << " "; + } + std::cout << "\n"; + } + + bool operator<(const EmissionPartial& other) const + { + if (m_pixel_id != other.m_pixel_id) + { + return m_pixel_id < other.m_pixel_id; + } + else + { + return m_depth < other.m_depth; + } + } + + inline void blend_absorption(const EmissionPartial& other) + { + const int num_bins = static_cast(m_bins.size()); + assert(num_bins == (int)other.m_bins.size()); + for (int i = 0; i < num_bins; ++i) + { + m_bins[i] *= other.m_bins[i]; + } + } + + inline void blend_emission(EmissionPartial& other) + { + const int num_bins = static_cast(m_bins.size()); + assert(num_bins == (int)other.m_bins.size()); + for (int i = 0; i < num_bins; ++i) + { + m_emission_bins[i] *= other.m_bins[i]; + } + } + + inline void add_emission(EmissionPartial& other) + { + const int num_bins = static_cast(m_bins.size()); + assert(num_bins == (int)other.m_bins.size()); + for (int i = 0; i < num_bins; ++i) + { + m_emission_bins[i] += other.m_emission_bins[i]; + } + } + + static void composite_background(std::vector& partials, + const std::vector& background) + { + //for( + } +}; + +} +} +} //vtkm::rendering::compositing + + +#endif //vtkm_rendering_comositing_emmsion_partial_h diff --git a/vtkm/rendering/compositing/Image.cxx b/vtkm/rendering/compositing/Image.cxx new file mode 100644 index 000000000..02fa20f93 --- /dev/null +++ b/vtkm/rendering/compositing/Image.cxx @@ -0,0 +1,35 @@ +// See License.txt + +#include +#include + +namespace vtkm +{ +namespace rendering +{ +namespace compositing +{ + +void Image::Save(const std::string& name, const std::vector& comments) +{ + vtkm::rendering::compositing::PNGEncoder encoder; + encoder.Encode(&this->Pixels[0], + this->Bounds.X.Max - this->Bounds.X.Min + 1, + this->Bounds.Y.Max - this->Bounds.Y.Min + 1, + comments); + encoder.Save(name); +} + +void Image::Save(const std::string& name, const std::vector& comments) const +{ + vtkm::rendering::compositing::PNGEncoder encoder; + encoder.Encode(&this->Pixels[0], + this->Bounds.X.Max - this->Bounds.X.Min + 1, + this->Bounds.Y.Max - this->Bounds.Y.Min + 1, + comments); + encoder.Save(name); +} + +} +} +} //namespace vtkm::rendering::compositing diff --git a/vtkm/rendering/compositing/Image.h b/vtkm/rendering/compositing/Image.h new file mode 100644 index 000000000..523069b0f --- /dev/null +++ b/vtkm/rendering/compositing/Image.h @@ -0,0 +1,333 @@ +//============================================================================ +// 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_rendering_compositing_Image_h +#define vtk_m_rendering_compositing_Image_h + +#include + +#include +#include +#include + +namespace vtkm +{ +namespace rendering +{ +namespace compositing +{ + +struct VTKM_RENDERING_EXPORT Image +{ + // The image bounds are indicated by a grid starting at + // 1-width and 1-height. Actual width would be calculated + // Bounds.X.Max - Bounds.X.Min + 1 + // 1024 - 1 + 1 = 1024 + vtkm::Bounds OrigBounds; + vtkm::Bounds Bounds; + std::vector Pixels; + std::vector Depths; + int OrigRank; + bool HasTransparency; + int CompositeOrder; + + Image() + : OrigRank(-1) + , HasTransparency(false) + , CompositeOrder(-1) + { + } + + + Image(const vtkm::Bounds& bounds) + : OrigBounds(bounds) + , Bounds(bounds) + , OrigRank(-1) + , HasTransparency(false) + , CompositeOrder(-1) + + { + const int dx = bounds.X.Max - bounds.X.Min + 1; + const int dy = bounds.Y.Max - bounds.Y.Min + 1; + this->Pixels.resize(dx * dy * 4); + this->Depths.resize(dx * dy); + } + + // init this image based on the original bounds + // of the other image + void InitOriginal(const Image& other) + { + this->OrigBounds = other.OrigBounds; + this->Bounds = other.OrigBounds; + + const int dx = this->Bounds.X.Max - this->Bounds.X.Min + 1; + const int dy = this->Bounds.Y.Max - this->Bounds.Y.Min + 1; + this->Pixels.resize(dx * dy * 4); + this->Depths.resize(dx * dy); + + this->OrigRank = -1; + this->HasTransparency = false; + this->CompositeOrder = -1; + } + + int GetNumberOfPixels() const { return static_cast(this->Pixels.size() / 4); } + + void SetHasTransparency(bool has_transparency) { this->HasTransparency = has_transparency; } + + bool GetHasTransparency() { return this->HasTransparency; } + + void Init(const float* color_buffer, + const float* depth_buffer, + vtkm::Id width, + vtkm::Id height, + int composite_order = -1) + { + this->CompositeOrder = composite_order; + this->Bounds.X.Min = 1; + this->Bounds.Y.Min = 1; + this->Bounds.X.Max = width; + this->Bounds.Y.Max = height; + this->OrigBounds = this->Bounds; + const int size = width * height; + this->Pixels.resize(size * 4); + this->Depths.resize(size); + +#ifdef VTKH_OPENMP_ENABLED +#pragma omp parallel for +#endif + for (int i = 0; i < size; ++i) + { + const int offset = i * 4; + this->Pixels[offset + 0] = static_cast(color_buffer[offset + 0] * 255.f); + this->Pixels[offset + 1] = static_cast(color_buffer[offset + 1] * 255.f); + this->Pixels[offset + 2] = static_cast(color_buffer[offset + 2] * 255.f); + this->Pixels[offset + 3] = static_cast(color_buffer[offset + 3] * 255.f); + float depth = depth_buffer[i]; + //make sure we can do a single comparison on depth + //deal with negative depth values + //TODO: This may not be the best way + depth = depth < 0 ? abs(depth) : depth; + this->Depths[i] = depth; + } + } + + void Init(const unsigned char* color_buffer, + const float* depth_buffer, + vtkm::Id width, + vtkm::Id height, + int composite_order = -1) + { + this->CompositeOrder = composite_order; + this->Bounds.X.Min = 1; + this->Bounds.Y.Min = 1; + this->Bounds.X.Max = width; + this->Bounds.Y.Max = height; + this->OrigBounds = this->Bounds; + + const int size = width * height; + this->Pixels.resize(size * 4); + this->Depths.resize(size); + + std::copy(color_buffer, color_buffer + size * 4, &this->Pixels[0]); + +#ifdef VTKH_OPENMP_ENABLED +#pragma omp parallel for +#endif + for (int i = 0; i < size; ++i) + { + float depth = depth_buffer[i]; + //make sure we can do a single comparison on depth + depth = depth < 0 ? 2.f : depth; + this->Depths[i] = depth; + } // for + } + + + void CompositeBackground(const float* color) + { + + const int size = static_cast(this->Pixels.size() / 4); + unsigned char bg_color[4]; + for (int i = 0; i < 4; ++i) + { + bg_color[i] = static_cast(color[i] * 255.f); + } + +#ifdef VTKH_OPENMP_ENABLED +#pragma omp parallel for +#endif + for (int i = 0; i < size; ++i) + { + const int offset = i * 4; + unsigned int alpha = static_cast(this->Pixels[offset + 3]); + const float opacity = (255 - alpha); + this->Pixels[offset + 0] += static_cast(opacity * bg_color[0] / 255); + this->Pixels[offset + 1] += static_cast(opacity * bg_color[1] / 255); + this->Pixels[offset + 2] += static_cast(opacity * bg_color[2] / 255); + this->Pixels[offset + 3] += static_cast(opacity * bg_color[3] / 255); + } + } + // + // Fill this image with a sub-region of another image + // + void SubsetFrom(const Image& image, const vtkm::Bounds& sub_region) + { + this->OrigBounds = image.OrigBounds; + this->Bounds = sub_region; + this->OrigRank = image.OrigRank; + this->CompositeOrder = image.CompositeOrder; + + assert(sub_region.X.Min >= image.Bounds.X.Min); + assert(sub_region.Y.Min >= image.Bounds.Y.Min); + assert(sub_region.X.Max <= image.Bounds.X.Max); + assert(sub_region.Y.Max <= image.Bounds.Y.Max); + + const int s_dx = this->Bounds.X.Max - this->Bounds.X.Min + 1; + const int s_dy = this->Bounds.Y.Max - this->Bounds.Y.Min + 1; + + const int dx = image.Bounds.X.Max - image.Bounds.X.Min + 1; + //const int dy = image.Bounds.Y.Max - image.Bounds.Y.Min + 1; + + const int start_x = this->Bounds.X.Min - image.Bounds.X.Min; + const int start_y = this->Bounds.Y.Min - image.Bounds.Y.Min; + const int end_y = start_y + s_dy; + + this->Pixels.resize(s_dx * s_dy * 4); + this->Depths.resize(s_dx * s_dy); + + + +#ifdef VTKH_OPENMP_ENABLED +#pragma omp parallel for +#endif + for (int y = start_y; y < end_y; ++y) + { + const int copy_to = (y - start_y) * s_dx; + const int copy_from = y * dx + start_x; + + std::copy(&image.Pixels[copy_from * 4], + &image.Pixels[copy_from * 4] + s_dx * 4, + &this->Pixels[copy_to * 4]); + std::copy(&image.Depths[copy_from], &image.Depths[copy_from] + s_dx, &this->Depths[copy_to]); + } + } + + void Color(int color) + { + unsigned char c[4]; + c[3] = 255; + + c[0] = 0; + c[1] = 0; + c[2] = 0; + int index = color % 3; + c[index] = 255 - color * 11; + ; + const int size = static_cast(this->Pixels.size()); + for (int i = 0; i < size; ++i) + { + float d = this->Depths[i / 4]; + if (d > 0 && d < 1) + { + this->Pixels[i] = c[i % 4]; + } + else + { + this->Pixels[i] = 155; + } + } + } + // + // Fills the passed in image with the contents of this image + // + void SubsetTo(Image& image) const + { + image.CompositeOrder = this->CompositeOrder; + assert(this->Bounds.X.Min >= image.Bounds.X.Min); + assert(this->Bounds.Y.Min >= image.Bounds.Y.Min); + assert(this->Bounds.X.Max <= image.Bounds.X.Max); + assert(this->Bounds.Y.Max <= image.Bounds.Y.Max); + + const int s_dx = this->Bounds.X.Max - this->Bounds.X.Min + 1; + const int s_dy = this->Bounds.Y.Max - this->Bounds.Y.Min + 1; + + const int dx = image.Bounds.X.Max - image.Bounds.X.Min + 1; + //const int dy = image.Bounds.Y.Max - image.Bounds.Y.Min + 1; + + const int start_x = this->Bounds.X.Min - image.Bounds.X.Min; + const int start_y = this->Bounds.Y.Min - image.Bounds.Y.Min; + +#ifdef VTKH_OPENMP_ENABLED +#pragma omp parallel for +#endif + for (int y = 0; y < s_dy; ++y) + { + const int copy_to = (y + start_y) * dx + start_x; + const int copy_from = y * s_dx; + + std::copy(&this->Pixels[copy_from * 4], + &this->Pixels[copy_from * 4] + s_dx * 4, + &image.Pixels[copy_to * 4]); + + std::copy(&this->Depths[copy_from], &this->Depths[copy_from] + s_dx, &image.Depths[copy_to]); + } + } + + void Swap(Image& other) + { + vtkm::Bounds orig = this->OrigBounds; + vtkm::Bounds bounds = this->Bounds; + + this->OrigBounds = other.OrigBounds; + this->Bounds = other.Bounds; + + other.OrigBounds = orig; + other.Bounds = bounds; + + this->Pixels.swap(other.Pixels); + this->Depths.swap(other.Depths); + } + + void Clear() + { + vtkm::Bounds empty; + this->OrigBounds = empty; + this->Bounds = empty; + this->Pixels.clear(); + this->Depths.clear(); + } + + std::string ToString() const + { + std::stringstream ss; + ss << "Total size pixels " << (int)this->Pixels.size() / 4; + ss << " tile dims: {" << this->Bounds.X.Min << "," << this->Bounds.Y.Min << "} - "; + ss << "{" << this->Bounds.X.Max << "," << this->Bounds.Y.Max << "}\n"; + ; + return ss.str(); + } + + void Save(const std::string& name, const std::vector& comments) const; + void Save(const std::string& name, const std::vector& comments); +}; + +struct CompositeOrderSort +{ + inline bool operator()(const Image& lhs, const Image& rhs) const + { + return lhs.CompositeOrder < rhs.CompositeOrder; + } +}; + +} +} +} //namespace vtkm::rendering::compositing + +#endif //vtk_m_rendering_compositing_Image_h diff --git a/vtkm/rendering/compositing/ImageCompositor.h b/vtkm/rendering/compositing/ImageCompositor.h new file mode 100644 index 000000000..ad8aff7dc --- /dev/null +++ b/vtkm/rendering/compositing/ImageCompositor.h @@ -0,0 +1,217 @@ +//============================================================================ +// 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_rendering_compositing_ImageCompositor_h +#define vtk_m_rendering_compositing_ImageCompositor_h + +#include + +#include +#include + +namespace vtkm +{ +namespace rendering +{ +namespace compositing +{ + +class VTKM_RENDERING_EXPORT ImageCompositor +{ +public: + void Blend(vtkm::rendering::compositing::Image& front, vtkm::rendering::compositing::Image& back) + { + assert(front.Bounds.X.Min == back.Bounds.X.Min); + assert(front.Bounds.Y.Min == back.Bounds.Y.Min); + assert(front.Bounds.X.Max == back.Bounds.X.Max); + assert(front.Bounds.Y.Max == back.Bounds.Y.Max); + const int size = static_cast(front.Pixels.size() / 4); + +#ifdef VTKH_OPENMP_ENABLED +#pragma omp parallel for +#endif + for (int i = 0; i < size; ++i) + { + const int offset = i * 4; + unsigned int alpha = front.Pixels[offset + 3]; + const unsigned int opacity = 255 - alpha; + + front.Pixels[offset + 0] += + static_cast(opacity * back.Pixels[offset + 0] / 255); + front.Pixels[offset + 1] += + static_cast(opacity * back.Pixels[offset + 1] / 255); + front.Pixels[offset + 2] += + static_cast(opacity * back.Pixels[offset + 2] / 255); + front.Pixels[offset + 3] += + static_cast(opacity * back.Pixels[offset + 3] / 255); + + float d1 = std::min(front.Depths[i], 1.001f); + float d2 = std::min(back.Depths[i], 1.001f); + float depth = std::min(d1, d2); + front.Depths[i] = depth; + } + } + + void ZBufferComposite(vtkm::rendering::compositing::Image& front, + const vtkm::rendering::compositing::Image& image) + { + assert(front.Depths.size() == front.Pixels.size() / 4); + assert(front.Bounds.X.Min == image.Bounds.X.Min); + assert(front.Bounds.Y.Min == image.Bounds.Y.Min); + assert(front.Bounds.X.Max == image.Bounds.X.Max); + assert(front.Bounds.Y.Max == image.Bounds.Y.Max); + + const int size = static_cast(front.Depths.size()); + +#ifdef VTKH_OPENMP_ENABLED +#pragma omp parallel for +#endif + for (int i = 0; i < size; ++i) + { + const float depth = image.Depths[i]; + if (depth > 1.f || front.Depths[i] < depth) + { + continue; + } + const int offset = i * 4; + front.Depths[i] = abs(depth); + front.Pixels[offset + 0] = image.Pixels[offset + 0]; + front.Pixels[offset + 1] = image.Pixels[offset + 1]; + front.Pixels[offset + 2] = image.Pixels[offset + 2]; + front.Pixels[offset + 3] = image.Pixels[offset + 3]; + } + } + + void OrderedComposite(std::vector& images) + { + const int total_images = images.size(); + std::sort(images.begin(), images.end(), CompositeOrderSort()); + for (int i = 1; i < total_images; ++i) + { + Blend(images[0], images[i]); + } + } + + void ZBufferComposite(std::vector& images) + { + const int total_images = images.size(); + for (int i = 1; i < total_images; ++i) + { + ZBufferComposite(images[0], images[i]); + } + } + + struct Pixel + { + unsigned char Color[4]; + float Depth; + int PixelId; // local (sub-image) pixels id + + bool operator<(const Pixel& other) const + { + if (this->PixelId != other.PixelId) + { + return this->PixelId < other.PixelId; + } + else + { + return this->Depth < other.Depth; + } + } + }; + + void CombineImages(const std::vector& images, + std::vector& pixels) + { + + const int num_images = static_cast(images.size()); + for (int i = 0; i < num_images; ++i) + { + // + // Extract the partial composites into a contiguous array + // + + const int image_size = images[i].GetNumberOfPixels(); + const int offset = i * image_size; +#ifdef VTKH_OPENMP_ENABLED +#pragma omp parallel for +#endif + for (int j = 0; j < image_size; ++j) + { + const int image_offset = j * 4; + pixels[offset + j].Color[0] = images[i].Pixels[image_offset + 0]; + pixels[offset + j].Color[1] = images[i].Pixels[image_offset + 1]; + pixels[offset + j].Color[2] = images[i].Pixels[image_offset + 2]; + pixels[offset + j].Color[3] = images[i].Pixels[image_offset + 3]; + pixels[offset + j].Depth = images[i].Depths[j]; + pixels[offset + j].PixelId = j; + } // for pixels + } // for images + } + + void ZBufferBlend(std::vector& images) + { + const int image_pixels = images[0].GetNumberOfPixels(); + const int num_images = static_cast(images.size()); + std::vector pixels; + CombineImages(images, pixels); +#ifdef VTKH_OPENMP_ENABLED +#pragma omp parallel for +#endif + for (int i = 0; i < image_pixels; ++i) + { + const int begin = image_pixels * i; + const int end = image_pixels * i - 1; + std::sort(pixels.begin() + begin, pixels.begin() + end); + } + + // check to see if that worked + int pixel_id_0 = pixels[0].PixelId; + for (int i = 1; i < num_images; ++i) + { + assert(pixel_id_0 == pixels[i].PixelId); + } + + +#ifdef VTKH_OPENMP_ENABLED +#pragma omp parallel for +#endif + for (int i = 0; i < image_pixels; ++i) + { + const int index = i * num_images; + Pixel pixel = pixels[index]; + for (int j = 1; j < num_images; ++j) + { + if (pixel.Color[3] == 255 || pixel.Depth > 1.f) + { + break; + } + unsigned int alpha = pixel.Color[3]; + const unsigned int opacity = 255 - alpha; + pixel.Color[0] += static_cast(opacity * pixels[index + j].Color[0] / 255); + pixel.Color[1] += static_cast(opacity * pixels[index + j].Color[1] / 255); + pixel.Color[2] += static_cast(opacity * pixels[index + j].Color[2] / 255); + pixel.Color[3] += static_cast(opacity * pixels[index + j].Color[3] / 255); + pixel.Depth = pixels[index + j].Depth; + } // for each image + images[0].Pixels[i * 4 + 0] = pixel.Color[0]; + images[0].Pixels[i * 4 + 1] = pixel.Color[1]; + images[0].Pixels[i * 4 + 2] = pixel.Color[2]; + images[0].Pixels[i * 4 + 3] = pixel.Color[3]; + images[0].Depths[i] = pixel.Depth; + } // for each pixel + } +}; + +} +} +} //namespace vtkm::rendering::compositing + +#endif //vtk_m_rendering_compositing_ImageComposititing_h diff --git a/vtkm/rendering/compositing/PNGEncoder.cxx b/vtkm/rendering/compositing/PNGEncoder.cxx new file mode 100644 index 000000000..64b920694 --- /dev/null +++ b/vtkm/rendering/compositing/PNGEncoder.cxx @@ -0,0 +1,246 @@ +//============================================================================ +// 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 + +VTKM_THIRDPARTY_PRE_INCLUDE +#include +VTKM_THIRDPARTY_POST_INCLUDE + +namespace vtkm +{ +namespace rendering +{ +namespace compositing +{ + +PNGEncoder::PNGEncoder() + : m_buffer(NULL) + , m_buffer_size(0) +{ +} + +PNGEncoder::~PNGEncoder() +{ + Cleanup(); +} + +void PNGEncoder::Encode(const unsigned char* rgba_in, const int width, const int height) +{ + Cleanup(); + + // upside down relative to what lodepng wants + unsigned char* rgba_flip = new unsigned char[width * height * 4]; + + for (int y = 0; y < height; ++y) + { + memcpy(&(rgba_flip[y * width * 4]), &(rgba_in[(height - y - 1) * width * 4]), width * 4); + } + + vtkm::png::LodePNGState state; + vtkm::png::lodepng_state_init(&state); + // use less aggressive compression + state.encoder.zlibsettings.btype = 2; + state.encoder.zlibsettings.use_lz77 = 0; + + unsigned error = lodepng_encode(&m_buffer, &m_buffer_size, &rgba_flip[0], width, height, &state); + delete[] rgba_flip; + + if (error) + { + std::cerr << "lodepng_encode_memory failed\n"; + } +} + +void PNGEncoder::Encode(const float* rgba_in, const int width, const int height) +{ + Cleanup(); + + // upside down relative to what lodepng wants + unsigned char* rgba_flip = new unsigned char[width * height * 4]; + + + for (int x = 0; x < width; ++x) + +#ifdef VTKH_OPENMP_ENABLED +#pragma omp parallel for +#endif + for (int y = 0; y < height; ++y) + { + int inOffset = (y * width + x) * 4; + int outOffset = ((height - y - 1) * width + x) * 4; + rgba_flip[outOffset + 0] = (unsigned char)(rgba_in[inOffset + 0] * 255.f); + rgba_flip[outOffset + 1] = (unsigned char)(rgba_in[inOffset + 1] * 255.f); + rgba_flip[outOffset + 2] = (unsigned char)(rgba_in[inOffset + 2] * 255.f); + rgba_flip[outOffset + 3] = (unsigned char)(rgba_in[inOffset + 3] * 255.f); + } + + vtkm::png::LodePNGState state; + vtkm::png::lodepng_state_init(&state); + // use less aggressive compression + state.encoder.zlibsettings.btype = 2; + state.encoder.zlibsettings.use_lz77 = 0; + + unsigned error = lodepng_encode(&m_buffer, &m_buffer_size, &rgba_flip[0], width, height, &state); + delete[] rgba_flip; + + if (error) + { + std::cerr << "lodepng_encode_memory failed\n"; + } +} + +void PNGEncoder::Encode(const unsigned char* rgba_in, + const int width, + const int height, + const std::vector& comments) +{ + Cleanup(); + + // upside down relative to what lodepng wants + unsigned char* rgba_flip = new unsigned char[width * height * 4]; + + for (int y = 0; y < height; ++y) + { + memcpy(&(rgba_flip[y * width * 4]), &(rgba_in[(height - y - 1) * width * 4]), width * 4); + } + + vtkm::png::LodePNGState state; + vtkm::png::lodepng_state_init(&state); + // use less aggressive compression + state.encoder.zlibsettings.btype = 2; + state.encoder.zlibsettings.use_lz77 = 0; + if (comments.size() % 2 != 0) + { + std::cerr << "PNGEncoder::Encode comments missing value for the last key.\n"; + std::cerr << "Ignoring the last key.\n"; + } + if (comments.size() > 1) + { + vtkm::png::lodepng_info_init(&state.info_png); + // Comments are in pairs with a key and a value, using + // comments.size()-1 ensures that we don't use the last + // comment if the length of the vector isn't a multiple of 2. + for (int i = 0; i < comments.size() - 1; i += 2) + vtkm::png::lodepng_add_text(&state.info_png, comments[i].c_str(), comments[i + 1].c_str()); + } + + unsigned error = + vtkm::png::lodepng_encode(&m_buffer, &m_buffer_size, &rgba_flip[0], width, height, &state); + delete[] rgba_flip; + + if (error) + { + std::cerr << "lodepng_encode_memory failed\n"; + } +} + +void PNGEncoder::Encode(const float* rgba_in, + const int width, + const int height, + const std::vector& comments) +{ + Cleanup(); + + // upside down relative to what lodepng wants + unsigned char* rgba_flip = new unsigned char[width * height * 4]; + + + for (int x = 0; x < width; ++x) + +#ifdef VTKH_OPENMP_ENABLED +#pragma omp parallel for +#endif + for (int y = 0; y < height; ++y) + { + int inOffset = (y * width + x) * 4; + int outOffset = ((height - y - 1) * width + x) * 4; + rgba_flip[outOffset + 0] = (unsigned char)(rgba_in[inOffset + 0] * 255.f); + rgba_flip[outOffset + 1] = (unsigned char)(rgba_in[inOffset + 1] * 255.f); + rgba_flip[outOffset + 2] = (unsigned char)(rgba_in[inOffset + 2] * 255.f); + rgba_flip[outOffset + 3] = (unsigned char)(rgba_in[inOffset + 3] * 255.f); + } + + vtkm::png::LodePNGState state; + vtkm::png::lodepng_state_init(&state); + // use less aggressive compression + state.encoder.zlibsettings.btype = 2; + state.encoder.zlibsettings.use_lz77 = 0; + if (comments.size() % 2 != 0) + { + std::cerr << "PNGEncoder::Encode comments missing value for the last key.\n"; + std::cerr << "Ignoring the last key.\n"; + } + if (comments.size() > 1) + { + vtkm::png::lodepng_info_init(&state.info_png); + // Comments are in pairs with a key and a value, using + // comments.size()-1 ensures that we don't use the last + // comment if the length of the vector isn't a multiple of 2. + for (int i = 0; i < comments.size() - 1; i += 2) + vtkm::png::lodepng_add_text(&state.info_png, comments[i].c_str(), comments[i + 1].c_str()); + } + + unsigned error = + vtkm::png::lodepng_encode(&m_buffer, &m_buffer_size, &rgba_flip[0], width, height, &state); + delete[] rgba_flip; + + if (error) + { + std::cerr << "lodepng_encode_memory failed\n"; + } +} + +void PNGEncoder::Save(const std::string& filename) +{ + if (m_buffer == NULL) + { + std::cerr << "Save must be called after encode()\n"; + /// we have a problem ...! + return; + } + + unsigned error = vtkm::png::lodepng_save_file(m_buffer, m_buffer_size, filename.c_str()); + if (error) + { + std::cerr << "Error saving PNG buffer to file: " << filename << "\n"; + } +} + +void* PNGEncoder::PngBuffer() +{ + return (void*)m_buffer; +} + +size_t PNGEncoder::PngBufferSize() +{ + return m_buffer_size; +} + +void PNGEncoder::Cleanup() +{ + if (m_buffer != NULL) + { + //lodepng_free(m_buffer); + // ^-- Not found even if LODEPNG_COMPILE_ALLOCATORS is defined? + // simply use "free" + free(m_buffer); + m_buffer = NULL; + m_buffer_size = 0; + } +} + +} +} +} //namespace vtkm::rendering::compositing diff --git a/vtkm/rendering/compositing/PNGEncoder.h b/vtkm/rendering/compositing/PNGEncoder.h new file mode 100644 index 000000000..d7f97ca67 --- /dev/null +++ b/vtkm/rendering/compositing/PNGEncoder.h @@ -0,0 +1,57 @@ +//============================================================================ +// 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_rendering_compositing_PNGEncoder_h +#define vtk_m_rendering_compositing_PNGEncoder_h + +#include +#include + +namespace vtkm +{ +namespace rendering +{ +namespace compositing +{ + +class PNGEncoder +{ +public: + PNGEncoder(); + ~PNGEncoder(); + + void Encode(const unsigned char* rgba_in, const int width, const int height); + void Encode(const float* rgba_in, const int width, const int height); + void Encode(const unsigned char* rgba_in, + const int width, + const int height, + const std::vector& comments); + void Encode(const float* rgba_in, + const int width, + const int height, + const std::vector& comments); + void Save(const std::string& filename); + + void* PngBuffer(); + size_t PngBufferSize(); + + void Cleanup(); + +private: + unsigned char* m_buffer; + size_t m_buffer_size; +}; + + +} +} +} //namespace vtkm::rendering::compositing + +#endif //vtk_m_rendering_compositing_PNGEncoder_h diff --git a/vtkm/rendering/compositing/PartialCompositor.cxx b/vtkm/rendering/compositing/PartialCompositor.cxx new file mode 100644 index 000000000..a06936f8a --- /dev/null +++ b/vtkm/rendering/compositing/PartialCompositor.cxx @@ -0,0 +1,552 @@ +//============================================================================ +// 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 + +#ifdef VTKM_ENABLE_MPI +#include +#include +#include +#endif + +namespace vtkm +{ +namespace rendering +{ +namespace compositing +{ + +namespace detail +{ +template