Nori  24
render.cpp
1 /*
2  This file is part of Nori, a simple educational ray tracer
3 
4  Copyright (c) 2015 by Wenzel Jakob, Romain Prévost
5 
6  Nori is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License Version 3
8  as published by the Free Software Foundation.
9 
10  Nori is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  GNU General Public License for more details.
14 
15  You should have received a copy of the GNU General Public License
16  along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18 
19 #include <nori/render.h>
20 #include <nori/parser.h>
21 #include <nori/scene.h>
22 #include <nori/camera.h>
23 #include <nori/block.h>
24 #include <nori/timer.h>
25 #include <nori/bitmap.h>
26 #include <nori/sampler.h>
27 #include <nori/integrator.h>
28 #include <nori/gui.h>
29 #include <tbb/parallel_for.h>
30 #include <tbb/blocked_range.h>
31 #include <tbb/task_scheduler_init.h>
32 #include <filesystem/resolver.h>
33 #include <tbb/concurrent_vector.h>
34 
35 
36 NORI_NAMESPACE_BEGIN
37 
38 RenderThread::RenderThread(ImageBlock & block) :
39  m_block(block)
40 {
41  m_render_status = 0;
42  m_progress = 1.f;
43 }
44 RenderThread::~RenderThread() {
45  stopRendering();
46 }
47 
48 bool RenderThread::isBusy() {
49  if(m_render_status == 3) {
50  m_render_thread.join();
51  m_render_status = 0;
52  }
53  return m_render_status != 0;
54 }
55 
56 void RenderThread::stopRendering() {
57  if(isBusy()) {
58  cout << "Requesting interruption of the current rendering" << endl;
59  m_render_status = 2;
60  m_render_thread.join();
61  m_render_status = 0;
62  cout << "Rendering successfully aborted" << endl;
63  }
64 }
65 
66 float RenderThread::getProgress() {
67  if(isBusy()) {
68  return m_progress;
69  }
70  else return 1.f;
71 }
72 
73 static void renderBlock(const Scene *scene, Sampler *sampler, ImageBlock &block) {
74  const Camera *camera = scene->getCamera();
75  const Integrator *integrator = scene->getIntegrator();
76 
77  Point2i offset = block.getOffset();
78  Vector2i size = block.getSize();
79 
80  /* Clear the block contents */
81  block.clear();
82 
83  /* For each pixel and pixel sample sample */
84  for (int y=0; y<size.y(); ++y) {
85  for (int x=0; x<size.x(); ++x) {
86  Point2f pixelSample = Point2f((float) (x + offset.x()), (float) (y + offset.y())) + sampler->next2D();
87  Point2f apertureSample = sampler->next2D();
88 
89  /* Sample a ray from the camera */
90  Ray3f ray;
91  Color3f value = camera->sampleRay(ray, pixelSample, apertureSample);
92 
93  /* Compute the incident radiance */
94  value *= integrator->Li(scene, sampler, ray);
95 
96  /* Store in the image block */
97  block.put(pixelSample, value);
98  }
99  }
100 }
101 
102 void RenderThread::renderScene(const std::string & filename) {
103 
104  filesystem::path path(filename);
105 
106  /* Add the parent directory of the scene file to the
107  file resolver. That way, the XML file can reference
108  resources (OBJ files, textures) using relative paths */
109  getFileResolver()->prepend(path.parent_path());
110 
111  NoriObject* root = loadFromXML(filename);
112 
113  // When the XML root object is a scene, start rendering it ..
114  if (root->getClassType() == NoriObject::EScene) {
115  m_scene = static_cast<Scene *>(root);
116 
117  const Camera *camera_ = m_scene->getCamera();
118  m_scene->getIntegrator()->preprocess(m_scene);
119 
120  /* Allocate memory for the entire output image and clear it */
121  m_block.init(camera_->getOutputSize(), camera_->getReconstructionFilter());
122  m_block.clear();
123 
124  /* Determine the filename of the output bitmap */
125  std::string outputNameStem = filename;
126  size_t lastdot = outputNameStem.find_last_of(".");
127  if (lastdot != std::string::npos)
128  outputNameStem.erase(lastdot, std::string::npos);
129 
130  /* Do the following in parallel and asynchronously */
131  m_render_status = 1;
132  m_progress = 0.f;
133  int n_threads = tbb::task_scheduler_init::automatic;
134  m_render_thread = std::thread([this, outputNameStem] {
135  tbb::task_scheduler_init init;
136  const Camera *camera = m_scene->getCamera();
137  Vector2i outputSize = camera->getOutputSize();
138 
139  /* Create a block generator (i.e. a work scheduler) */
140  BlockGenerator blockGenerator(outputSize, NORI_BLOCK_SIZE);
141 
142  cout << "Rendering .. ";
143  cout.flush();
144  Timer timer;
145 
146  auto numSamples = m_scene->getSampler()->getSampleCount();
147  auto numBlocks = blockGenerator.getBlockCount();
148 
149  tbb::concurrent_vector< std::unique_ptr<Sampler> > samplers;
150  samplers.resize(numBlocks);
151 
152  for (uint32_t k = 0; k < numSamples ; ++k) {
153  m_progress = k/float(numSamples);
154  if(m_render_status == 2)
155  break;
156 
157  tbb::blocked_range<int> range(0, numBlocks);
158 
159  auto map = [&](const tbb::blocked_range<int> &range) {
160  // Allocate memory for a small image block to be rendered by the current thread
161  ImageBlock block(Vector2i(NORI_BLOCK_SIZE),
162  camera->getReconstructionFilter());
163 
164  for (int i = range.begin(); i < range.end(); ++i) {
165  // Request an image block from the block generator
166  blockGenerator.next(block);
167 
168  // Get block id to continue using the same sampler
169  auto blockId = block.getBlockId();
170  if(k == 0) { // Initialize the sampler for the first sample
171  std::unique_ptr<Sampler> sampler(m_scene->getSampler()->clone());
172  sampler->prepare(block);
173  samplers.at(blockId) = std::move(sampler);
174  }
175 
176  // Render all contained pixels
177  renderBlock(m_scene, samplers.at(blockId).get(), block);
178 
179  // The image block has been processed. Now add it to the "big" block that represents the entire image
180  m_block.put(block);
181  }
182  };
183 
185  //map(range);
186 
188  tbb::parallel_for(range, map);
189 
190  blockGenerator.reset();
191  }
192 
193  cout << "done. (took " << timer.elapsedString() << ")" << endl;
194 
195  /* Now turn the rendered image block into
196  a properly normalized bitmap */
197  m_block.lock();
198  std::unique_ptr<Bitmap> bitmap(m_block.toBitmap());
199  m_block.unlock();
200 
201  /* Save using the OpenEXR and PNG formats */
202  bitmap->save(outputNameStem);
203 
204  delete m_scene;
205  m_scene = nullptr;
206 
207  m_render_status = 3;
208  });
209 
210  }
211  else {
212  delete root;
213  }
214 
215 }
216 
217 
218 NORI_NAMESPACE_END
Spiraling block generator.
Definition: block.h:137
int getBlockCount() const
Return the total number of blocks.
Definition: block.h:165
void reset()
Reset to the first block.
Definition: block.cpp:148
bool next(ImageBlock &block)
Return the next block to be rendered.
Definition: block.cpp:156
Generic camera interface.
Definition: camera.h:35
const Vector2i & getOutputSize() const
Return the size of the output image in pixels.
Definition: camera.h:62
virtual Color3f sampleRay(Ray3f &ray, const Point2f &samplePosition, const Point2f &apertureSample) const =0
Importance sample a ray according to the camera's response function.
const ReconstructionFilter * getReconstructionFilter() const
Return the camera's reconstruction filter in image space.
Definition: camera.h:65
Weighted pixel storage for a rectangular subregion of an image.
Definition: block.h:48
void put(const Point2f &pos, const Color3f &value)
Record a sample with the given position and radiance value.
Definition: block.cpp:93
void unlock() const
Unlock the image block.
Definition: block.h:112
const Vector2i & getSize() const
Return the size of the block within the main image.
Definition: block.h:75
void clear()
Clear all contents.
Definition: block.h:95
void lock() const
Lock the image block (using an internal mutex)
Definition: block.h:109
Bitmap * toBitmap() const
Turn the block into a proper bitmap.
Definition: block.cpp:76
const Point2i & getOffset() const
Return the offset of the block within the main image.
Definition: block.h:69
Abstract integrator (i.e. a rendering technique)
Definition: integrator.h:35
virtual Color3f Li(const Scene *scene, Sampler *sampler, const Ray3f &ray) const =0
Sample the incident radiance along a ray.
virtual void preprocess(const Scene *scene)
Perform an (optional) preprocess step.
Definition: integrator.h:41
Base class of all objects.
Definition: object.h:32
virtual EClassType getClassType() const =0
Return the type of object (i.e. Mesh/BSDF/etc.) provided by this instance.
void renderScene(const std::string &filename)
Definition: render.cpp:102
Abstract sample generator.
Definition: sampler.h:63
virtual std::unique_ptr< Sampler > clone() const =0
Create an exact clone of the current instance.
virtual Point2f next2D()=0
Retrieve the next two component values from the current sample.
virtual size_t getSampleCount() const
Return the number of configured pixel samples.
Definition: sampler.h:99
Main scene data structure.
Definition: scene.h:34
const Camera * getCamera() const
Return a pointer to the scene's camera.
Definition: scene.h:52
const Integrator * getIntegrator() const
Return a pointer to the scene's integrator.
Definition: scene.h:46
const Sampler * getSampler() const
Return a pointer to the scene's sample generator (const version)
Definition: scene.h:55
Simple timer with millisecond precision.
Definition: timer.h:32
std::string elapsedString(bool precise=false) const
Like elapsed(), but return a human-readable string.
Definition: timer.h:48
Represents a linear RGB color value.
Definition: color.h:29