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) 2014-2017 Hans Hoogenboom, Esteban Tovagliari, The appleseedhq Organization |
9 | // |
10 | // Permission is hereby granted, free of charge, to any person obtaining a copy |
11 | // of this software and associated documentation files (the "Software"), to deal |
12 | // in the Software without restriction, including without limitation the rights |
13 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
14 | // copies of the Software, and to permit persons to whom the Software is |
15 | // furnished to do so, subject to the following conditions: |
16 | // |
17 | // The above copyright notice and this permission notice shall be included in |
18 | // all copies or substantial portions of the Software. |
19 | // |
20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
25 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
26 | // THE SOFTWARE. |
27 | // |
28 | |
29 | // This code is based on SideFX's tomdisplay and deepmplay examples distributed with Houdini. |
30 | |
31 | // Interface header. |
32 | #include "houdinitilecallbacks.h" |
33 | |
34 | // appleseed.renderer headers. |
35 | #include "renderer/kernel/aov/imagestack.h" |
36 | #include "renderer/modeling/frame/frame.h" |
37 | |
38 | // appleseed.foundation headers. |
39 | #include "foundation/image/canvasproperties.h" |
40 | #include "foundation/image/image.h" |
41 | #include "foundation/image/pixel.h" |
42 | #include "foundation/platform/thread.h" |
43 | #include "foundation/utility/log.h" |
44 | #include "foundation/utility/otherwise.h" |
45 | |
46 | // Boost headers. |
47 | #include "boost/lexical_cast.hpp" |
48 | |
49 | // Standard headers. |
50 | #include <cstdio> |
51 | #include <cstring> |
52 | #include <string> |
53 | |
54 | using namespace foundation; |
55 | using namespace renderer; |
56 | using namespace std; |
57 | |
58 | namespace appleseed { |
59 | namespace cli { |
60 | |
61 | // |
62 | // HoudiniTileCallback. |
63 | // |
64 | |
65 | namespace |
66 | { |
67 | class HoudiniTileCallback |
68 | : public TileCallbackBase |
69 | { |
70 | public: |
71 | // mplay constructor. |
72 | HoudiniTileCallback( |
73 | const char* scene_name, |
74 | const bool progressive_mode, |
75 | Logger& logger) |
76 | : m_logger(logger) |
77 | , m_fp(0) |
78 | , m_header_sent(false) |
79 | , m_single_plane(false) |
80 | { |
81 | string cmd("imdisplay -p -f " ); |
82 | |
83 | if (progressive_mode) |
84 | cmd += "-k " ; |
85 | |
86 | cmd += "-n " ; |
87 | cmd += scene_name; |
88 | |
89 | LOG_DEBUG(m_logger, "executing imdisplay: command = %s" , cmd.c_str()); |
90 | |
91 | m_fp = open_pipe(cmd.c_str()); |
92 | if (!m_fp) |
93 | LOG_FATAL(m_logger, "Unable to open mplay" ); |
94 | } |
95 | |
96 | // hrmanpipe constructor. |
97 | HoudiniTileCallback( |
98 | const int socket_number, |
99 | const bool progressive_mode, |
100 | Logger& logger) |
101 | : m_logger(logger) |
102 | , m_fp(0) |
103 | , m_header_sent(false) |
104 | , m_single_plane(true) |
105 | { |
106 | string cmd("hrmanpipe -f " ); |
107 | |
108 | if (progressive_mode) |
109 | cmd += "-m " ; |
110 | |
111 | cmd += boost::lexical_cast<string>(socket_number); |
112 | |
113 | LOG_DEBUG(m_logger, "executing hrmanpipe: command = %s" , cmd.c_str()); |
114 | |
115 | m_fp = open_pipe(cmd.c_str()); |
116 | |
117 | if (m_fp == 0) |
118 | LOG_FATAL(m_logger, "Unable to open hrmanpipe" ); |
119 | } |
120 | |
121 | ~HoudiniTileCallback() |
122 | { |
123 | if (m_fp) |
124 | close_pipe(m_fp); |
125 | } |
126 | |
127 | virtual void release() APPLESEED_OVERRIDE |
128 | { |
129 | // Do nothing. |
130 | } |
131 | |
132 | virtual void post_render_tile( |
133 | const Frame* frame, |
134 | const size_t tile_x, |
135 | const size_t tile_y) APPLESEED_OVERRIDE |
136 | { |
137 | boost::mutex::scoped_lock lock(m_mutex); |
138 | send_header(*frame); |
139 | send_tile(*frame, tile_x, tile_y); |
140 | } |
141 | |
142 | virtual void post_render(const Frame* frame) APPLESEED_OVERRIDE |
143 | { |
144 | boost::mutex::scoped_lock lock(m_mutex); |
145 | send_header(*frame); |
146 | |
147 | const CanvasProperties& frame_props = frame->image().properties(); |
148 | |
149 | for (size_t ty = 0; ty < frame_props.m_tile_count_y; ++ty) |
150 | for (size_t tx = 0; tx < frame_props.m_tile_count_x; ++tx) |
151 | send_tile(*frame, tx, ty); |
152 | } |
153 | |
154 | private: |
155 | static FILE* open_pipe(const char* command) |
156 | { |
157 | #ifdef _WIN32 |
158 | return _popen(command, "wb" ); |
159 | #else |
160 | return popen(command, "w" ); |
161 | #endif |
162 | } |
163 | |
164 | static void close_pipe(FILE* pipe) |
165 | { |
166 | #ifdef _WIN32 |
167 | _pclose(pipe); |
168 | #else |
169 | pclose(pipe); |
170 | #endif |
171 | } |
172 | |
173 | void (const Frame& frame) |
174 | { |
175 | if (!m_header_sent) |
176 | { |
177 | { |
178 | int [8]; |
179 | memset(header, 0, sizeof(header)); |
180 | |
181 | header[0] = (('h' << 24) + ('M' << 16) + ('P'<< 8) + ('0')); |
182 | header[1] = static_cast<int>(frame.image().properties().m_canvas_width); |
183 | header[2] = static_cast<int>(frame.image().properties().m_canvas_height); |
184 | |
185 | if (m_single_plane) |
186 | { |
187 | header[3] = map_pixel_format(frame.image().properties().m_pixel_format); |
188 | header[4] = static_cast<int>(frame.image().properties().m_channel_count); |
189 | } |
190 | else |
191 | { |
192 | header[5] = static_cast<int>(1 + frame.aov_images().size()); |
193 | } |
194 | |
195 | if (fwrite(header, sizeof(int), 8, m_fp) != 8) |
196 | LOG_FATAL(m_logger, "Unable to write header" ); |
197 | } |
198 | |
199 | send_plane_definition(frame.image(), "beauty" , 0); |
200 | |
201 | if (!m_single_plane) |
202 | { |
203 | for (size_t i = 0, e = frame.aov_images().size(); i != e; ++i) |
204 | { |
205 | send_plane_definition( |
206 | frame.aov_images().get_image(i), |
207 | frame.aov_images().get_name(i), |
208 | i + 1); |
209 | } |
210 | } |
211 | |
212 | m_header_sent = true; |
213 | } |
214 | } |
215 | |
216 | void send_plane_definition( |
217 | const Image& img, |
218 | const char* name, |
219 | const size_t index) const |
220 | { |
221 | int plane_def[8]; |
222 | memset(plane_def, 0, sizeof(plane_def)); |
223 | |
224 | plane_def[0] = static_cast<int>(index); |
225 | plane_def[1] = static_cast<int>(strlen(name)); |
226 | plane_def[2] = map_pixel_format(img.properties().m_pixel_format); |
227 | |
228 | if (img.properties().m_pixel_format == PixelFormatDouble) |
229 | LOG_WARNING(m_logger, "Houdini does not support double pixels, converting to float" ); |
230 | |
231 | plane_def[3] = static_cast<int>(img.properties().m_channel_count); |
232 | |
233 | if (fwrite(plane_def, sizeof(int), 8, m_fp) != 8) |
234 | LOG_FATAL(m_logger, "Error sending plane definition" ); |
235 | |
236 | if (fwrite(name, sizeof(char), plane_def[1], m_fp) != plane_def[1]) |
237 | LOG_FATAL(m_logger, "Error sending plane name" ); |
238 | } |
239 | |
240 | void send_tile( |
241 | const Frame& frame, |
242 | const size_t tile_x, |
243 | const size_t tile_y) const |
244 | { |
245 | // We assume all AOV images have the same properties as the main image. |
246 | const CanvasProperties& props = frame.image().properties(); |
247 | |
248 | // Send beauty tile. |
249 | do_send_tile( |
250 | props, |
251 | frame.image().tile(tile_x, tile_y), |
252 | tile_x, |
253 | tile_y, |
254 | 0); |
255 | |
256 | if (!m_single_plane) |
257 | { |
258 | // Send AOV tiles. |
259 | for (size_t i = 0, e = frame.aov_images().size(); i < e; ++i) |
260 | { |
261 | do_send_tile( |
262 | props, |
263 | frame.aov_images().get_image(i).tile(tile_x, tile_y), |
264 | tile_x, |
265 | tile_y, |
266 | i + 1); |
267 | } |
268 | } |
269 | } |
270 | |
271 | void do_send_tile( |
272 | const CanvasProperties& properties, |
273 | const Tile& tile, |
274 | const size_t tile_x, |
275 | const size_t tile_y, |
276 | const size_t plane_index) const |
277 | { |
278 | int tile_head[4]; |
279 | |
280 | // First, tell the reader what the plane index is for the data being |
281 | // sent. This is done by sending a special tile header. The x0 |
282 | // coordinate is set to -1 to indicate the special header. The x1 |
283 | // coordinate is set to the plane index. The Y coordinates must be |
284 | // zero. |
285 | tile_head[0] = -1; |
286 | tile_head[1] = static_cast<int>(plane_index); |
287 | tile_head[2] = 0; |
288 | tile_head[3] = 0; |
289 | |
290 | if (fwrite(tile_head, sizeof(int), 4, m_fp) != 4) |
291 | LOG_FATAL(m_logger, "Error sending tile index" ); |
292 | |
293 | // Send tile header. |
294 | tile_head[0] = static_cast<int>(tile_x * properties.m_tile_width); |
295 | tile_head[1] = static_cast<int>(tile_head[0] + tile.get_width() - 1); |
296 | tile_head[2] = static_cast<int>(tile_y * properties.m_tile_height); |
297 | tile_head[3] = static_cast<int>(tile_head[2] + tile.get_height() - 1); |
298 | |
299 | if (fwrite(tile_head, sizeof(int), 4, m_fp) != 4) |
300 | LOG_FATAL(m_logger, "Error sending tile header" ); |
301 | |
302 | // Send tile pixels. |
303 | if (properties.m_pixel_format == PixelFormatHalf || |
304 | properties.m_pixel_format == PixelFormatDouble) |
305 | { |
306 | const Tile tmp(tile, PixelFormatFloat); |
307 | if (fwrite(tmp.get_storage(), 1, tmp.get_size(), m_fp) != tmp.get_size()) |
308 | LOG_FATAL(m_logger, "Error sending tile pixels" ); |
309 | } |
310 | else |
311 | { |
312 | if (fwrite(tile.get_storage(), 1, tile.get_size(), m_fp) != tile.get_size()) |
313 | LOG_FATAL(m_logger, "Error sending tile pixels" ); |
314 | } |
315 | |
316 | fflush(m_fp); |
317 | } |
318 | |
319 | int map_pixel_format(const PixelFormat pixel_format) const |
320 | { |
321 | switch (pixel_format) |
322 | { |
323 | case PixelFormatUInt8: |
324 | return 1; |
325 | |
326 | case PixelFormatUInt16: |
327 | return 2; |
328 | |
329 | case PixelFormatUInt32: |
330 | return 4; |
331 | |
332 | case PixelFormatHalf: |
333 | case PixelFormatFloat: |
334 | case PixelFormatDouble: |
335 | return 0; |
336 | |
337 | assert_otherwise; |
338 | } |
339 | |
340 | return -1; |
341 | } |
342 | |
343 | Logger& m_logger; |
344 | boost::mutex m_mutex; |
345 | FILE* m_fp; |
346 | bool ; |
347 | bool m_single_plane; |
348 | }; |
349 | } |
350 | |
351 | |
352 | // |
353 | // MPlayTileCallbackFactory class implementation. |
354 | // |
355 | |
356 | MPlayTileCallbackFactory::MPlayTileCallbackFactory( |
357 | const char* scene_name, |
358 | const bool progressive_mode, |
359 | Logger& logger) |
360 | : m_callback( |
361 | new HoudiniTileCallback( |
362 | scene_name, |
363 | progressive_mode, |
364 | logger)) |
365 | { |
366 | } |
367 | |
368 | void MPlayTileCallbackFactory::release() |
369 | { |
370 | delete this; |
371 | } |
372 | |
373 | ITileCallback* MPlayTileCallbackFactory::create() |
374 | { |
375 | return m_callback.get(); |
376 | } |
377 | |
378 | |
379 | // |
380 | // HRmanPipeTileCallbackFactory class implementation. |
381 | // |
382 | |
383 | HRmanPipeTileCallbackFactory::HRmanPipeTileCallbackFactory( |
384 | const int socket_number, |
385 | const bool progressive_mode, |
386 | Logger& logger) |
387 | : m_callback( |
388 | new HoudiniTileCallback( |
389 | socket_number, |
390 | progressive_mode, |
391 | logger)) |
392 | { |
393 | } |
394 | |
395 | void HRmanPipeTileCallbackFactory::release() |
396 | { |
397 | delete this; |
398 | } |
399 | |
400 | ITileCallback* HRmanPipeTileCallbackFactory::create() |
401 | { |
402 | return m_callback.get(); |
403 | } |
404 | |
405 | } // namespace cli |
406 | } // namespace appleseed |
407 | |