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
54using namespace foundation;
55using namespace renderer;
56using namespace std;
57
58namespace appleseed {
59namespace cli {
60
61//
62// HoudiniTileCallback.
63//
64
65namespace
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 send_header(const Frame& frame)
174 {
175 if (!m_header_sent)
176 {
177 {
178 int header[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 m_header_sent;
347 bool m_single_plane;
348 };
349}
350
351
352//
353// MPlayTileCallbackFactory class implementation.
354//
355
356MPlayTileCallbackFactory::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
368void MPlayTileCallbackFactory::release()
369{
370 delete this;
371}
372
373ITileCallback* MPlayTileCallbackFactory::create()
374{
375 return m_callback.get();
376}
377
378
379//
380// HRmanPipeTileCallbackFactory class implementation.
381//
382
383HRmanPipeTileCallbackFactory::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
395void HRmanPipeTileCallbackFactory::release()
396{
397 delete this;
398}
399
400ITileCallback* HRmanPipeTileCallbackFactory::create()
401{
402 return m_callback.get();
403}
404
405} // namespace cli
406} // namespace appleseed
407