1
2//
3// This source file is part of appleseed.
4// Visit http://appleseedhq.net/ for additional information and resources.
5//
6// This software is released under the MIT license.
7//
8// Copyright (c) 2010-2013 Francois Beaune, Jupiter Jazz Limited
9// Copyright (c) 2014-2017 Francois Beaune, The appleseedhq Organization
10//
11// Permission is hereby granted, free of charge, to any person obtaining a copy
12// of this software and associated documentation files (the "Software"), to deal
13// in the Software without restriction, including without limitation the rights
14// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15// copies of the Software, and to permit persons to whom the Software is
16// furnished to do so, subject to the following conditions:
17//
18// The above copyright notice and this permission notice shall be included in
19// all copies or substantial portions of the Software.
20//
21// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27// THE SOFTWARE.
28//
29
30// Interface header.
31#include "generictilerenderer.h"
32
33// appleseed.renderer headers.
34#include "renderer/global/globallogger.h"
35#include "renderer/global/globaltypes.h"
36#include "renderer/kernel/aov/imagestack.h"
37#include "renderer/kernel/aov/tilestack.h"
38#include "renderer/kernel/rendering/ipixelrenderer.h"
39#include "renderer/kernel/rendering/ishadingresultframebufferfactory.h"
40#include "renderer/kernel/rendering/pixelcontext.h"
41#include "renderer/kernel/rendering/shadingresultframebuffer.h"
42#include "renderer/modeling/frame/frame.h"
43
44// appleseed.foundation headers.
45#include "foundation/image/canvasproperties.h"
46#include "foundation/image/image.h"
47#include "foundation/image/tile.h"
48#include "foundation/math/aabb.h"
49#include "foundation/math/filter.h"
50#include "foundation/math/hash.h"
51#include "foundation/math/ordering.h"
52#include "foundation/math/scalar.h"
53#include "foundation/math/vector.h"
54#include "foundation/platform/arch.h"
55#include "foundation/platform/breakpoint.h"
56#include "foundation/platform/types.h"
57#include "foundation/utility/autoreleaseptr.h"
58#include "foundation/utility/iostreamop.h"
59#include "foundation/utility/job.h"
60#include "foundation/utility/statistics.h"
61#include "foundation/utility/string.h"
62
63// Standard headers.
64#include <cassert>
65#include <cmath>
66#include <memory>
67#include <string>
68#include <vector>
69
70using namespace foundation;
71using namespace std;
72
73namespace renderer
74{
75
76namespace
77{
78 //
79 // Generic tile renderer.
80 //
81
82#ifndef NDEBUG
83
84 // Define this symbol to break execution into the debugger
85 // when a specific pixel is about to be rendered.
86 // #define DEBUG_BREAK_AT_PIXEL Vector2i(0, 0)
87
88#endif
89
90 class GenericTileRenderer
91 : public ITileRenderer
92 {
93 public:
94 GenericTileRenderer(
95 const Frame& frame,
96 IPixelRendererFactory* pixel_renderer_factory,
97 IShadingResultFrameBufferFactory* framebuffer_factory,
98 const ParamArray& params,
99 const size_t thread_index)
100 : m_pixel_renderer(pixel_renderer_factory->create(thread_index))
101 , m_framebuffer_factory(framebuffer_factory)
102 {
103 compute_tile_margins(frame, thread_index == 0);
104 compute_pixel_ordering(frame);
105 }
106
107 virtual void release() APPLESEED_OVERRIDE
108 {
109 delete this;
110 }
111
112 virtual void render_tile(
113 const Frame& frame,
114 const size_t tile_x,
115 const size_t tile_y,
116 const size_t pass_hash,
117 IAbortSwitch& abort_switch) APPLESEED_OVERRIDE
118 {
119 // Retrieve frame properties.
120 const CanvasProperties& frame_properties = frame.image().properties();
121 assert(tile_x < frame_properties.m_tile_count_x);
122 assert(tile_y < frame_properties.m_tile_count_y);
123
124 // Retrieve tile properties.
125 Tile& tile = frame.image().tile(tile_x, tile_y);
126 TileStack aov_tiles = frame.aov_images().tiles(tile_x, tile_y);
127 const int tile_origin_x = static_cast<int>(frame_properties.m_tile_width * tile_x);
128 const int tile_origin_y = static_cast<int>(frame_properties.m_tile_height * tile_y);
129
130 // Compute the image space bounding box of the pixels to render.
131 AABB2i tile_bbox;
132 tile_bbox.min.x = tile_origin_x;
133 tile_bbox.min.y = tile_origin_y;
134 tile_bbox.max.x = tile_origin_x + static_cast<int>(tile.get_width()) - 1;
135 tile_bbox.max.y = tile_origin_y + static_cast<int>(tile.get_height()) - 1;
136 tile_bbox = AABB2i::intersect(tile_bbox, AABB2i(frame.get_crop_window()));
137 if (!tile_bbox.is_valid())
138 return;
139
140 // Transform the bounding box to local (tile) space.
141 tile_bbox.min.x -= tile_origin_x;
142 tile_bbox.min.y -= tile_origin_y;
143 tile_bbox.max.x -= tile_origin_x;
144 tile_bbox.max.y -= tile_origin_y;
145
146 // Pad the bounding box with tile margins.
147 AABB2i padded_tile_bbox;
148 padded_tile_bbox.min.x = tile_bbox.min.x - m_margin_width;
149 padded_tile_bbox.min.y = tile_bbox.min.y - m_margin_height;
150 padded_tile_bbox.max.x = tile_bbox.max.x + m_margin_width;
151 padded_tile_bbox.max.y = tile_bbox.max.y + m_margin_height;
152
153 // Inform the pixel renderer that we are about to render a tile.
154 m_pixel_renderer->on_tile_begin(frame, tile, aov_tiles);
155
156 // Create the framebuffer into which we will accumulate the samples.
157 ShadingResultFrameBuffer* framebuffer =
158 m_framebuffer_factory->create(
159 frame,
160 tile_x,
161 tile_y,
162 tile_bbox);
163 assert(framebuffer);
164
165 // Seed the RNG with the tile index and the pass hash.
166 // Seeding the RNG per tile instead of per pixel has potential consequences on
167 // debugging: rendering a subset of a tile may lead to different computations
168 // than rendering the full tile, e.g. if the sampling context switches to random
169 // sampling because the number of dimensions becomes too high.
170 const size_t tile_index = tile_y * frame_properties.m_tile_count_x + tile_x;
171#ifdef APPLESEED_ARCH64
172 m_rng = SamplingContext::RNGType(hash_uint64_to_uint32(pass_hash ^ tile_index));
173#else
174 m_rng = SamplingContext::RNGType(pass_hash ^ tile_index);
175#endif
176
177 // Loop over tile pixels.
178 for (size_t i = 0, e = m_pixel_ordering.size(); i < e; ++i)
179 {
180 // Cancel any work done on this tile if rendering is aborted.
181 if (abort_switch.is_aborted())
182 return;
183
184 // Retrieve the coordinates of the pixel in the padded tile.
185 const Vector2i pt(m_pixel_ordering[i].x, m_pixel_ordering[i].y);
186
187 // Skip pixels outside the intersection of the padded tile and the crop window.
188 if (!padded_tile_bbox.contains(pt))
189 continue;
190
191 const Vector2i pi(tile_origin_x + pt.x, tile_origin_y + pt.y);
192
193#ifdef DEBUG_BREAK_AT_PIXEL
194
195 // Break in the debugger when this pixel is reached.
196 if (pi == DEBUG_BREAK_AT_PIXEL)
197 BREAKPOINT();
198
199#endif
200
201 // Render this pixel.
202 m_pixel_renderer->render_pixel(
203 frame,
204 tile,
205 aov_tiles,
206 tile_bbox,
207 pass_hash,
208 pi,
209 pt,
210 m_rng,
211 *framebuffer);
212 }
213
214 // Develop the framebuffer to the tile.
215 if (frame.is_premultiplied_alpha())
216 framebuffer->develop_to_tile_premult_alpha(tile, aov_tiles);
217 else framebuffer->develop_to_tile_straight_alpha(tile, aov_tiles);
218
219 // Release the framebuffer.
220 m_framebuffer_factory->destroy(framebuffer);
221
222 // Inform the pixel renderer that we are done rendering the tile.
223 m_pixel_renderer->on_tile_end(frame, tile, aov_tiles);
224 }
225
226 virtual StatisticsVector get_statistics() const APPLESEED_OVERRIDE
227 {
228 return m_pixel_renderer->get_statistics();
229 }
230
231 protected:
232 auto_release_ptr<IPixelRenderer> m_pixel_renderer;
233 IShadingResultFrameBufferFactory* m_framebuffer_factory;
234 int m_margin_width;
235 int m_margin_height;
236 vector<Vector<int16, 2> > m_pixel_ordering;
237 SamplingContext::RNGType m_rng;
238
239 void compute_tile_margins(const Frame& frame, const bool primary)
240 {
241 m_margin_width = truncate<int>(ceil(frame.get_filter().get_xradius() - 0.5f));
242 m_margin_height = truncate<int>(ceil(frame.get_filter().get_yradius() - 0.5f));
243
244 const CanvasProperties& properties = frame.image().properties();
245 const size_t padded_tile_width = properties.m_tile_width + 2 * m_margin_width;
246 const size_t padded_tile_height = properties.m_tile_height + 2 * m_margin_height;
247 const size_t padded_pixel_count = padded_tile_width * padded_tile_height;
248 const size_t pixel_count = properties.m_tile_width * properties.m_tile_height;
249 const size_t overhead_pixel_count = padded_pixel_count - pixel_count;
250 const double wasted_effort = static_cast<double>(overhead_pixel_count) / pixel_count * 100.0;
251 const double MaxWastedEffort = 15.0; // percents
252
253 if (primary)
254 {
255 RENDERER_LOG(
256 wasted_effort > MaxWastedEffort ? LogMessage::Warning : LogMessage::Info,
257 "rendering effort wasted by tile borders: %s (tile dimensions: %s x %s, tile margins: %s x %s)",
258 pretty_percent(overhead_pixel_count, pixel_count).c_str(),
259 pretty_uint(properties.m_tile_width).c_str(),
260 pretty_uint(properties.m_tile_height).c_str(),
261 pretty_uint(2 * m_margin_width).c_str(),
262 pretty_uint(2 * m_margin_height).c_str());
263 }
264 }
265
266 void compute_pixel_ordering(const Frame& frame)
267 {
268 // Compute the dimensions in pixels of the padded tile.
269 const CanvasProperties& properties = frame.image().properties();
270 const size_t padded_tile_width = properties.m_tile_width + 2 * m_margin_width;
271 const size_t padded_tile_height = properties.m_tile_height + 2 * m_margin_height;
272 const size_t pixel_count = padded_tile_width * padded_tile_height;
273
274 // Generate the pixel ordering inside the padded tile.
275 vector<size_t> ordering;
276 ordering.reserve(pixel_count);
277 hilbert_ordering(ordering, padded_tile_width, padded_tile_height);
278 assert(ordering.size() == pixel_count);
279
280 // Convert the pixel ordering to a 2D representation.
281 m_pixel_ordering.resize(pixel_count);
282 for (size_t i = 0; i < pixel_count; ++i)
283 {
284 const size_t x = ordering[i] % padded_tile_width;
285 const size_t y = ordering[i] / padded_tile_width;
286 assert(x < padded_tile_width);
287 assert(y < padded_tile_height);
288 m_pixel_ordering[i].x = static_cast<int16>(x) - m_margin_width;
289 m_pixel_ordering[i].y = static_cast<int16>(y) - m_margin_height;
290 }
291 }
292 };
293}
294
295
296//
297// GenericTileRendererFactory class implementation.
298//
299
300GenericTileRendererFactory::GenericTileRendererFactory(
301 const Frame& frame,
302 IPixelRendererFactory* pixel_renderer_factory,
303 IShadingResultFrameBufferFactory* framebuffer_factory,
304 const ParamArray& params)
305 : m_frame(frame)
306 , m_pixel_renderer_factory(pixel_renderer_factory)
307 , m_framebuffer_factory(framebuffer_factory)
308 , m_params(params)
309{
310}
311
312void GenericTileRendererFactory::release()
313{
314 delete this;
315}
316
317ITileRenderer* GenericTileRendererFactory::create(
318 const size_t thread_index)
319{
320 return
321 new GenericTileRenderer(
322 m_frame,
323 m_pixel_renderer_factory,
324 m_framebuffer_factory,
325 m_params,
326 thread_index);
327}
328
329} // namespace renderer
330