diff --git a/examples/simple/CMakeLists.txt b/examples/simple/CMakeLists.txt index a45c7189..01300b12 100644 --- a/examples/simple/CMakeLists.txt +++ b/examples/simple/CMakeLists.txt @@ -65,6 +65,13 @@ target_link_libraries(anariTutorialInitFromEnv PRIVATE anari::anari) add_executable(anariInfo anariInfo.cpp) target_link_libraries(anariInfo PRIVATE anari::anari_static) +option(USE_MPI "Enable MPI and Distributed Tutorial") +if (USE_MPI) + find_package(MPI REQUIRED) + add_executable(anariDistributedTutorial anariDistributedTutorial.cpp) + target_link_libraries(anariDistributedTutorial PRIVATE anari::anari stb_image MPI::MPI_CXX) +endif() + if (IN_SDK_SOURCE_TREE) install(TARGETS anariInfo RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/examples/simple/anariDistributedTutorial.cpp b/examples/simple/anariDistributedTutorial.cpp new file mode 100644 index 00000000..5b71b074 --- /dev/null +++ b/examples/simple/anariDistributedTutorial.cpp @@ -0,0 +1,181 @@ +// Copyright 2024 The Khronos Group +// SPDX-License-Identifier: Apache-2.0 + +#include +#include +#include +#include +#include + +// anari +#define ANARI_EXTENSION_UTILITY_IMPL +#include "anari/anari_cpp.hpp" +#include "anari/anari_cpp/ext/std.h" +// stb_image +#include "stb_image_write.h" + +using namespace anari::std_types; + +static void statusFunc(const void *userData, + ANARIDevice device, + ANARIObject source, + ANARIDataType sourceType, + ANARIStatusSeverity severity, + ANARIStatusCode code, + const char *message) +{ + (void)userData; + (void)device; + (void)source; + (void)sourceType; + (void)code; + if (severity == ANARI_SEVERITY_FATAL_ERROR) { + fprintf(stderr, "[FATAL] %s\n", message); + } else if (severity == ANARI_SEVERITY_ERROR) { + fprintf(stderr, "[ERROR] %s\n", message); + } else if (severity == ANARI_SEVERITY_WARNING) { + fprintf(stderr, "[WARN ] %s\n", message); + } else if (severity == ANARI_SEVERITY_PERFORMANCE_WARNING) { + fprintf(stderr, "[PERF ] %s\n", message); + } else if (severity == ANARI_SEVERITY_INFO) { + fprintf(stderr, "[INFO ] %s\n", message); + } else if (severity == ANARI_SEVERITY_DEBUG) { + fprintf(stderr, "[DEBUG] %s\n", message); + } +} + +static void onFrameCompletion(const void *, anari::Device d, anari::Frame f) +{ + printf("anari::Device(%p) finished rendering anari::Frame(%p)!\n", d, f); +} + +int main(int argc, char **argv) +{ + int mpiThreadCapability = 0; + MPI_Init_thread(&argc, &argv, MPI_THREAD_MULTIPLE, &mpiThreadCapability); + if (mpiThreadCapability != MPI_THREAD_MULTIPLE + && mpiThreadCapability != MPI_THREAD_SERIALIZED) { + fprintf(stderr, + "MPI runtime must support either thread multiple or thread serialized.\n"); + return 1; + } + + int mpiRank = 0; + int mpiWorldSize = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &mpiRank); + MPI_Comm_size(MPI_COMM_WORLD, &mpiWorldSize); + + // image size + uvec2 imgSize = {1024 /*width*/, 768 /*height*/}; + + // camera + vec3 cam_pos{(mpiWorldSize + 1.f) / 2.f, 0.5f, -mpiWorldSize * 0.5f}; + vec3 cam_up{0.f, 1.f, 0.f}; + vec3 cam_view{0.f, 0.f, 1.f}; + + // all ranks specify the same rendering parameters, with the exception of + // the data to be rendered, which is distributed among the ranks + // triangle mesh data + vec3 vertex[] = {{(float)mpiRank, 0.0f, 3.5f}, + {(float)mpiRank, 1.0f, 3.0f}, + {mpiRank + 1.f, 0.0f, 3.0f}, + {mpiRank + 1.f, 1.0f, 2.5f}}; + vec4 color[] = {{0.0f, 0.0f, (mpiRank + 1.f) / mpiWorldSize, 1.0f}, + {0.0f, 0.0f, (mpiRank + 1.f) / mpiWorldSize, 1.0f}, + {0.0f, 0.0f, (mpiRank + 1.f) / mpiWorldSize, 1.0f}, + {0.0f, 0.0f, (mpiRank + 1.f) / mpiWorldSize, 1.0f}}; + uvec3 index[] = {{0, 1, 2}, {1, 2, 3}}; + + anari::Library lib = anari::loadLibrary("environment", statusFunc); + + anari::Extensions extensions = + anari::extension::getDeviceExtensionStruct(lib, "default"); + + if (!extensions.ANARI_KHR_GEOMETRY_TRIANGLE) + printf("WARNING: device doesn't support ANARI_KHR_GEOMETRY_TRIANGLE\n"); + if (!extensions.ANARI_KHR_CAMERA_PERSPECTIVE) + printf("WARNING: device doesn't support ANARI_KHR_CAMERA_PERSPECTIVE\n"); + if (!extensions.ANARI_KHR_MATERIAL_MATTE) + printf("WARNING: device doesn't support ANARI_KHR_MATERIAL_MATTE\n"); + if (!extensions.ANARI_KHR_FRAME_COMPLETION_CALLBACK) { + printf( + "INFO: device doesn't support ANARI_KHR_FRAME_COMPLETION_CALLBACK\n"); + } + + anari::Device d = anari::newDevice(lib, "distributed"); + + // create and setup camera + auto camera = anari::newObject(d, "perspective"); + anari::setParameter( + d, camera, "aspect", (float)imgSize[0] / (float)imgSize[1]); + anari::setParameter(d, camera, "position", cam_pos); + anari::setParameter(d, camera, "direction", cam_view); + anari::setParameter(d, camera, "up", cam_up); + anari::commitParameters( + d, camera); // commit objects to indicate setting parameters is done + + // create and setup surface and mesh + auto mesh = anari::newObject(d, "triangle"); + anari::setParameterArray1D(d, mesh, "vertex.position", vertex, 4); + anari::setParameterArray1D(d, mesh, "vertex.color", color, 4); + anari::setParameterArray1D(d, mesh, "primitive.index", index, 2); + anari::commitParameters(d, mesh); + + auto mat = anari::newObject(d, "matte"); + anari::setParameter(d, mat, "color", "color"); + anari::commitParameters(d, mat); + + // put the mesh into a surface + auto surface = anari::newObject(d); + anari::setAndReleaseParameter(d, surface, "geometry", mesh); + anari::setAndReleaseParameter(d, surface, "material", mat); + anari::commitParameters(d, surface); + + // put the surface directly onto the world + auto world = anari::newObject(d); + anari::setParameterArray1D(d, world, "surface", &surface, 1); + anari::release(d, surface); + anari::commitParameters(d, world); + + auto renderer = anari::newObject(d, "default"); + anari::commitParameters(d, renderer); + + // create and setup frame + auto frame = anari::newObject(d); + anari::setParameter(d, frame, "size", imgSize); + anari::setParameter(d, frame, "channel.color", ANARI_UFIXED8_RGBA_SRGB); + anari::setAndReleaseParameter(d, frame, "renderer", renderer); + anari::setAndReleaseParameter(d, frame, "camera", camera); + anari::setAndReleaseParameter(d, frame, "world", world); + anari::setParameter(d, + frame, + "frameCompletionCallback", + (anari::FrameCompletionCallback)onFrameCompletion); + anari::commitParameters(d, frame); + + // render one frame + anari::render(d, frame); + anari::wait(d, frame); + + // on rank 0, access frame and write its content as PNG file + if (mpiRank == 0) { + auto fb = anari::map(d, frame, "channel.color"); + stbi_flip_vertically_on_write(1); + stbi_write_png("tutorialDistributed.png", + int(fb.width), + int(fb.height), + 4, + fb.data, + 4 * int(fb.width)); + anari::unmap(d, frame, "channel.color"); + } + + // final cleanups + anari::release(d, frame); + anari::release(d, d); + anari::unloadLibrary(lib); + + MPI_Finalize(); + + return 0; +} diff --git a/examples/simple/anariTutorial.cpp b/examples/simple/anariTutorial.cpp index af29a07c..b40f4f21 100644 --- a/examples/simple/anariTutorial.cpp +++ b/examples/simple/anariTutorial.cpp @@ -191,7 +191,7 @@ int main(int argc, const char **argv) anari::commitParameters(d, frame); - printf("rendering frame to firstFrame.png...\n"); + printf("rendering frame to tutorial_cpp.png...\n"); // render one frame anari::render(d, frame);