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