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 | |
70 | using namespace foundation; |
71 | using namespace std; |
72 | |
73 | namespace renderer |
74 | { |
75 | |
76 | namespace |
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 | |
300 | GenericTileRendererFactory::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 | |
312 | void GenericTileRendererFactory::release() |
313 | { |
314 | delete this; |
315 | } |
316 | |
317 | ITileRenderer* 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 | |