1 // Copyright 2009-2021 Intel Corporation
2 // SPDX-License-Identifier: Apache-2.0
3 
4 /* This is a small example tutorial how to use OSPRay and the
5  * MPIDistributedDevice in a data-parallel application.
6  * Each rank must specify the same render parameters, however the data
7  * to render on each rank can differ for distributed rendering. In this
8  * tutorial each rank renders a unique quad, which is colored by the rank.
9  *
10  * On Linux build it in the build_directory with:
11  *   mpicc -std=c99 ../modules/mpi/tutorials/ospMPIDistributedTutorial.c \
12  *       -I ../ospray/include \
13  *       -L . -lospray -Wl,-rpath,. \
14  *       -o ospMPIDistributedTutorial
15  *
16  * Then run it with MPI on some number of processes
17  *   mpirun -n <N> ./ospMPIDistributedTutorial
18  *
19  * The output image should show a sequence of quads, from dark to light blue
20  */
21 
22 #include <errno.h>
23 #include <mpi.h>
24 #include <stdint.h>
25 #include <stdio.h>
26 #ifdef _WIN32
27 #include <malloc.h>
28 #else
29 #include <stdlib.h>
30 #endif
31 #include <ospray/ospray.h>
32 #include <ospray/ospray_util.h>
33 
34 // helper function to write the rendered image as PPM file
writePPM(const char * fileName,int size_x,int size_y,const uint32_t * pixel)35 void writePPM(
36     const char *fileName, int size_x, int size_y, const uint32_t *pixel)
37 {
38   FILE *file = fopen(fileName, "wb");
39   if (!file) {
40     fprintf(stderr, "fopen('%s', 'wb') failed: %d", fileName, errno);
41     return;
42   }
43   fprintf(file, "P6\n%i %i\n255\n", size_x, size_y);
44   unsigned char *out = (unsigned char *)alloca(3 * size_x);
45   for (int y = 0; y < size_y; y++) {
46     const unsigned char *in =
47         (const unsigned char *)&pixel[(size_y - 1 - y) * size_x];
48     for (int x = 0; x < size_x; x++) {
49       out[3 * x + 0] = in[4 * x + 0];
50       out[3 * x + 1] = in[4 * x + 1];
51       out[3 * x + 2] = in[4 * x + 2];
52     }
53     fwrite(out, 3 * size_x, sizeof(char), file);
54   }
55   fprintf(file, "\n");
56   fclose(file);
57 }
58 
main(int argc,char ** argv)59 int main(int argc, char **argv)
60 {
61   int mpiThreadCapability = 0;
62   MPI_Init_thread(&argc, &argv, MPI_THREAD_MULTIPLE, &mpiThreadCapability);
63   if (mpiThreadCapability != MPI_THREAD_MULTIPLE
64       && mpiThreadCapability != MPI_THREAD_SERIALIZED) {
65     fprintf(stderr,
66         "OSPRay requires the MPI runtime to support thread "
67         "multiple or thread serialized.\n");
68     return 1;
69   }
70 
71   int mpiRank = 0;
72   int mpiWorldSize = 0;
73   MPI_Comm_rank(MPI_COMM_WORLD, &mpiRank);
74   MPI_Comm_size(MPI_COMM_WORLD, &mpiWorldSize);
75 
76   // image size
77   int imgSizeX = 1024; // width
78   int imgSizeY = 768; // height
79 
80   // camera
81   float cam_pos[] = {(mpiWorldSize + 1.f) / 2.f, 0.5f, -mpiWorldSize * 0.5f};
82   float cam_up[] = {0.f, 1.f, 0.f};
83   float cam_view[] = {0.0f, 0.f, 1.f};
84 
85   // all ranks specify the same rendering parameters, with the exception of
86   // the data to be rendered, which is distributed among the ranks
87   // triangle mesh data
88   float vertex[] = {
89       mpiRank,
90       0.0f,
91       3.5f,
92       mpiRank,
93       1.0f,
94       3.0f,
95       1.0f * (mpiRank + 1.f),
96       0.0f,
97       3.0f,
98       1.0f * (mpiRank + 1.f),
99       1.0f,
100       2.5f,
101   };
102   float color[] = {0.0f,
103       0.0f,
104       (mpiRank + 1.f) / mpiWorldSize,
105       1.0f,
106       0.0f,
107       0.0f,
108       (mpiRank + 1.f) / mpiWorldSize,
109       1.0f,
110       0.0f,
111       0.0f,
112       (mpiRank + 1.f) / mpiWorldSize,
113       1.0f,
114       0.0f,
115       0.0f,
116       (mpiRank + 1.f) / mpiWorldSize,
117       1.0f};
118   int32_t index[] = {0, 1, 2, 1, 2, 3};
119 
120   // load the MPI module, and select the MPI distributed device. Here we
121   // do not call ospInit, as we want to explicitly pick the distributed
122   // device. This can also be done by passing --osp:mpi-distributed when
123   // using ospInit, however if the user doesn't pass this argument your
124   // application will likely not behave as expected
125   ospLoadModule("mpi");
126 
127   OSPDevice mpiDevice = ospNewDevice("mpiDistributed");
128   ospDeviceCommit(mpiDevice);
129   ospSetCurrentDevice(mpiDevice);
130 
131   // create and setup camera
132   OSPCamera camera = ospNewCamera("perspective");
133   ospSetFloat(camera, "aspect", imgSizeX / (float)imgSizeY);
134   ospSetParam(camera, "position", OSP_VEC3F, cam_pos);
135   ospSetParam(camera, "direction", OSP_VEC3F, cam_view);
136   ospSetParam(camera, "up", OSP_VEC3F, cam_up);
137   ospCommit(camera); // commit each object to indicate modifications are done
138 
139   // create and setup model and mesh
140   OSPGeometry mesh = ospNewGeometry("mesh");
141   OSPData data = ospNewSharedData1D(vertex, OSP_VEC3F, 4);
142   ospCommit(data);
143   ospSetObject(mesh, "vertex.position", data);
144   ospRelease(data); // we are done using this handle
145 
146   data = ospNewSharedData1D(color, OSP_VEC4F, 4);
147   ospCommit(data);
148   ospSetObject(mesh, "vertex.color", data);
149   ospRelease(data); // we are done using this handle
150 
151   data = ospNewSharedData1D(index, OSP_VEC3UI, 2);
152   ospCommit(data);
153   ospSetObject(mesh, "index", data);
154   ospRelease(data); // we are done using this handle
155 
156   ospCommit(mesh);
157 
158   // put the mesh into a model
159   OSPGeometricModel model = ospNewGeometricModel(mesh);
160   ospCommit(model);
161   ospRelease(mesh);
162 
163   // put the model into a group (collection of models)
164   OSPGroup group = ospNewGroup();
165   OSPData geometricModels = ospNewSharedData1D(&model, OSP_GEOMETRIC_MODEL, 1);
166   ospSetObject(group, "geometry", geometricModels);
167   ospCommit(group);
168   ospRelease(model);
169   ospRelease(geometricModels);
170 
171   // put the group into an instance (give the group a world transform)
172   OSPInstance instance = ospNewInstance(group);
173   ospCommit(instance);
174   ospRelease(group);
175 
176   // put the instance in the world
177   OSPWorld world = ospNewWorld();
178   OSPData instances = ospNewSharedData1D(&instance, OSP_INSTANCE, 1);
179   ospSetObject(world, "instance", instances);
180   ospRelease(instance);
181   ospRelease(instances);
182 
183   // In the distributed device we set a clipping region to clip to the data
184   // owned uniquely by this rank which it should be rendering
185   float regionBounds[] = {mpiRank, 0.f, 2.5f, 1.f * (mpiRank + 1.f), 1.f, 3.5f};
186   data = ospNewSharedData1D(regionBounds, OSP_BOX3F, 1);
187   ospCommit(data);
188   ospSetObject(world, "region", data);
189   ospRelease(data);
190 
191   ospCommit(world);
192 
193   // create the mpi_raycast renderer (requred for distributed rendering)
194   OSPRenderer renderer = ospNewRenderer("mpiRaycast");
195 
196   // create and setup light for Ambient Occlusion
197   // TODO: Who gets the lights now?
198   OSPLight light = ospNewLight("ambient");
199   ospCommit(light);
200   OSPData lights = ospNewSharedData1D(&light, OSP_LIGHT, 1);
201   ospCommit(lights);
202 
203   // complete setup of renderer
204   ospSetFloat(renderer, "backgroundColor", 1.0f); // white, transparent
205   ospSetObject(renderer, "light", lights);
206   ospCommit(renderer);
207 
208   // create and setup framebuffer
209   OSPFrameBuffer framebuffer = ospNewFrameBuffer(imgSizeX,
210       imgSizeY,
211       OSP_FB_SRGBA,
212       OSP_FB_COLOR | /*OSP_FB_DEPTH |*/ OSP_FB_ACCUM);
213   ospResetAccumulation(framebuffer);
214 
215   // Try picking an object
216   OSPPickResult pickResult;
217   ospPick(&pickResult, framebuffer, renderer, camera, world, 0.5f, 0.5f);
218   if (pickResult.hasHit) {
219     printf(
220         "Rank %d: ospPick() center of screen --> [inst: %p, model: %p, prim: %u]\n",
221         mpiRank,
222         pickResult.instance,
223         pickResult.model,
224         pickResult.primID);
225     ospRelease(pickResult.instance);
226     ospRelease(pickResult.model);
227   } else {
228     printf("Rank %d: ospPick() center of screen did not hit\n", mpiRank);
229   }
230 
231   // render one frame
232   ospRenderFrameBlocking(framebuffer, renderer, camera, world);
233 
234   // on rank 0, access framebuffer and write its content as PPM file
235   if (mpiRank == 0) {
236     const uint32_t *fb =
237         (uint32_t *)ospMapFrameBuffer(framebuffer, OSP_FB_COLOR);
238     writePPM("firstFrame.ppm", imgSizeX, imgSizeY, fb);
239     ospUnmapFrameBuffer(fb, framebuffer);
240   }
241 
242   // render 10 more frames, which are accumulated to result in a better
243   // converged image
244   for (int frames = 0; frames < 10; frames++)
245     ospRenderFrameBlocking(framebuffer, renderer, camera, world);
246 
247   if (mpiRank == 0) {
248     const uint32_t *fb =
249         (uint32_t *)ospMapFrameBuffer(framebuffer, OSP_FB_COLOR);
250     writePPM("accumulatedFrame.ppm", imgSizeX, imgSizeY, fb);
251     ospUnmapFrameBuffer(fb, framebuffer);
252   }
253 
254   // final cleanups
255   ospRelease(renderer);
256   ospRelease(camera);
257   ospRelease(lights);
258   ospRelease(light);
259   ospRelease(framebuffer);
260   ospRelease(world);
261   ospDeviceRelease(mpiDevice);
262 
263   ospShutdown();
264 
265   MPI_Finalize();
266 
267   return 0;
268 }
269