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 | // appleseed.cli headers. |
31 | #include "commandlinehandler.h" |
32 | #include "continuoussavingtilecallback.h" |
33 | #include "houdinitilecallbacks.h" |
34 | #include "progresstilecallback.h" |
35 | |
36 | // appleseed.renderer headers. |
37 | #include "renderer/api/color.h" |
38 | #include "renderer/api/frame.h" |
39 | #include "renderer/api/log.h" |
40 | #include "renderer/api/material.h" |
41 | #include "renderer/api/object.h" |
42 | #include "renderer/api/project.h" |
43 | #include "renderer/api/rendering.h" |
44 | #include "renderer/api/scene.h" |
45 | #include "renderer/api/surfaceshader.h" |
46 | #include "renderer/api/utility.h" |
47 | |
48 | // appleseed.foundation headers. |
49 | #include "foundation/platform/thread.h" |
50 | #include "foundation/platform/timers.h" |
51 | #include "foundation/utility/autoreleaseptr.h" |
52 | #include "foundation/utility/benchmark.h" |
53 | #include "foundation/utility/filter.h" |
54 | #include "foundation/utility/log.h" |
55 | #include "foundation/utility/stopwatch.h" |
56 | #include "foundation/utility/string.h" |
57 | #include "foundation/utility/test.h" |
58 | |
59 | // appleseed.shared headers. |
60 | #include "application/application.h" |
61 | #include "application/superlogger.h" |
62 | |
63 | // appleseed.main headers. |
64 | #include "main/allocator.h" |
65 | |
66 | // Boost headers. |
67 | #include "boost/filesystem/operations.hpp" |
68 | #include "boost/filesystem/path.hpp" |
69 | |
70 | // Standard headers. |
71 | #include <cstddef> |
72 | #include <cstdlib> |
73 | #include <memory> |
74 | #include <string> |
75 | |
76 | using namespace appleseed::cli; |
77 | using namespace appleseed::shared; |
78 | using namespace foundation; |
79 | using namespace renderer; |
80 | using namespace std; |
81 | namespace bf = boost::filesystem; |
82 | |
83 | namespace |
84 | { |
85 | SuperLogger g_logger; |
86 | ParamArray g_settings; |
87 | CommandLineHandler g_cl; |
88 | |
89 | template <typename Result> |
90 | void print_suite_case_result(const Result& result) |
91 | { |
92 | const size_t suite_exec = result.get_suite_execution_count(); |
93 | const size_t suite_fail = result.get_suite_failure_count(); |
94 | const size_t case_exec = result.get_case_execution_count(); |
95 | const size_t case_fail = result.get_case_failure_count(); |
96 | |
97 | LOG_INFO( |
98 | g_logger, |
99 | " suites : %s executed, %s failed (%s)\n" |
100 | " cases : %s executed, %s failed (%s)" , |
101 | pretty_uint(suite_exec).c_str(), |
102 | pretty_uint(suite_fail).c_str(), |
103 | pretty_percent(suite_fail, suite_exec).c_str(), |
104 | pretty_uint(case_exec).c_str(), |
105 | pretty_uint(case_fail).c_str(), |
106 | pretty_percent(case_fail, case_exec).c_str()); |
107 | } |
108 | |
109 | void print_unit_test_result(const TestResult& result) |
110 | { |
111 | LOG_INFO(g_logger, "unit testing summary:" ); |
112 | print_suite_case_result(result); |
113 | |
114 | const size_t assert_exec = result.get_assertion_execution_count(); |
115 | const size_t assert_fail = result.get_assertion_failure_count(); |
116 | |
117 | LOG_INFO( |
118 | g_logger, |
119 | " assertions : %s executed, %s failed (%s)" , |
120 | pretty_uint(assert_exec).c_str(), |
121 | pretty_uint(assert_fail).c_str(), |
122 | pretty_percent(assert_fail, assert_exec).c_str()); |
123 | } |
124 | |
125 | void print_unit_benchmark_result(const BenchmarkResult& result) |
126 | { |
127 | LOG_INFO(g_logger, "unit benchmarking summary:" ); |
128 | print_suite_case_result(result); |
129 | } |
130 | |
131 | bool run_unit_tests() |
132 | { |
133 | // Configure our logger. |
134 | SaveLogFormatterConfig save_g_logger_config(g_logger); |
135 | g_logger.set_all_formats("{datetime-utc} | {message}" ); |
136 | |
137 | // Configure the renderer's logger: mute all log messages except warnings and errors. |
138 | SaveLogFormatterConfig save_global_logger_config(global_logger()); |
139 | global_logger().set_all_formats(string()); |
140 | global_logger().reset_format(LogMessage::Warning); |
141 | global_logger().reset_format(LogMessage::Error); |
142 | global_logger().reset_format(LogMessage::Fatal); |
143 | |
144 | // Create a test listener that outputs to the logger. |
145 | auto_release_ptr<ITestListener> listener( |
146 | create_logger_test_listener( |
147 | g_logger, |
148 | g_cl.m_verbose_unit_tests.is_set())); |
149 | |
150 | TestResult result; |
151 | |
152 | const bf::path old_current_path = |
153 | Application::change_current_directory_to_tests_root_path(); |
154 | |
155 | // Run test suites. |
156 | if (g_cl.m_run_unit_tests.values().empty()) |
157 | TestSuiteRepository::instance().run(listener.ref(), result); |
158 | else |
159 | { |
160 | const char* regex = g_cl.m_run_unit_tests.value().c_str(); |
161 | const RegExFilter filter(regex, RegExFilter::CaseInsensitive); |
162 | |
163 | if (filter.is_valid()) |
164 | TestSuiteRepository::instance().run(filter, listener.ref(), result); |
165 | else |
166 | { |
167 | LOG_ERROR( |
168 | g_logger, |
169 | "malformed regular expression '%s', disabling test filtering." , |
170 | regex); |
171 | TestSuiteRepository::instance().run(listener.ref(), result); |
172 | } |
173 | } |
174 | |
175 | // Restore the current directory. |
176 | bf::current_path(old_current_path); |
177 | |
178 | print_unit_test_result(result); |
179 | |
180 | return result.get_assertion_failure_count() == 0; |
181 | } |
182 | |
183 | void run_unit_benchmarks() |
184 | { |
185 | // Configure our logger. |
186 | SaveLogFormatterConfig save_g_logger_config(g_logger); |
187 | g_logger.set_all_formats("{datetime-utc} | {message}" ); |
188 | |
189 | // Configure the renderer's logger: mute all log messages except warnings and errors. |
190 | SaveLogFormatterConfig save_global_logger_config(global_logger()); |
191 | global_logger().set_all_formats(string()); |
192 | global_logger().reset_format(LogMessage::Warning); |
193 | global_logger().reset_format(LogMessage::Error); |
194 | global_logger().reset_format(LogMessage::Fatal); |
195 | |
196 | BenchmarkResult result; |
197 | |
198 | // Add a benchmark listener that outputs to the logger. |
199 | auto_release_ptr<IBenchmarkListener> |
200 | logger_listener(create_logger_benchmark_listener(g_logger)); |
201 | result.add_listener(logger_listener.get()); |
202 | |
203 | // Try to add a benchmark listener that outputs to a XML file. |
204 | auto_release_ptr<XMLFileBenchmarkListener> xmlfile_listener( |
205 | create_xmlfile_benchmark_listener()); |
206 | const string xmlfile_name = "benchmark." + get_time_stamp_string() + ".xml" ; |
207 | const bf::path xmlfile_path = |
208 | bf::path(Application::get_tests_root_path()) |
209 | / "unit benchmarks" / "results" / xmlfile_name; |
210 | if (xmlfile_listener->open(xmlfile_path.string().c_str())) |
211 | result.add_listener(xmlfile_listener.get()); |
212 | else |
213 | { |
214 | LOG_WARNING( |
215 | g_logger, |
216 | "automatic benchmark results archiving to %s failed: i/o error." , |
217 | xmlfile_path.string().c_str()); |
218 | } |
219 | |
220 | const bf::path old_current_path = |
221 | Application::change_current_directory_to_tests_root_path(); |
222 | |
223 | // Run benchmark suites. |
224 | if (g_cl.m_run_unit_benchmarks.values().empty()) |
225 | BenchmarkSuiteRepository::instance().run(result); |
226 | else |
227 | { |
228 | const char* regex = g_cl.m_run_unit_benchmarks.value().c_str(); |
229 | const RegExFilter filter(regex, RegExFilter::CaseInsensitive); |
230 | |
231 | if (filter.is_valid()) |
232 | BenchmarkSuiteRepository::instance().run(filter, result); |
233 | else |
234 | { |
235 | LOG_ERROR( |
236 | g_logger, |
237 | "malformed regular expression '%s', disabling benchmark filtering." , |
238 | regex); |
239 | BenchmarkSuiteRepository::instance().run(result); |
240 | } |
241 | } |
242 | |
243 | // Restore the current directory. |
244 | bf::current_path(old_current_path); |
245 | |
246 | // Print results. |
247 | print_unit_benchmark_result(result); |
248 | } |
249 | |
250 | void set_frame_parameter(Project& project, const string& key, const string& value) |
251 | { |
252 | const Frame* frame = project.get_frame(); |
253 | assert(frame); |
254 | |
255 | ParamArray params = frame->get_parameters(); |
256 | params.insert(key, value); |
257 | |
258 | auto_release_ptr<Frame> new_frame(FrameFactory::create(frame->get_name(), params)); |
259 | |
260 | project.set_frame(new_frame); |
261 | } |
262 | |
263 | void apply_resolution_command_line_option(Project& project) |
264 | { |
265 | if (g_cl.m_resolution.is_set()) |
266 | { |
267 | const string resolution = |
268 | foundation::to_string(g_cl.m_resolution.values()[0]) + ' ' + |
269 | foundation::to_string(g_cl.m_resolution.values()[1]); |
270 | |
271 | set_frame_parameter(project, "resolution" , resolution); |
272 | } |
273 | } |
274 | |
275 | void apply_crop_window_command_line_option(Project& project) |
276 | { |
277 | if (g_cl.m_window.is_set()) |
278 | { |
279 | const string crop_window = |
280 | foundation::to_string(g_cl.m_window.values()[0]) + ' ' + |
281 | foundation::to_string(g_cl.m_window.values()[1]) + ' ' + |
282 | foundation::to_string(g_cl.m_window.values()[2]) + ' ' + |
283 | foundation::to_string(g_cl.m_window.values()[3]); |
284 | |
285 | set_frame_parameter(project, "crop_window" , crop_window); |
286 | } |
287 | } |
288 | |
289 | void apply_samples_command_line_option(ParamArray& params) |
290 | { |
291 | if (g_cl.m_samples.is_set()) |
292 | { |
293 | params.insert_path( |
294 | "uniform_pixel_renderer.samples" , |
295 | g_cl.m_samples.values()[1]); |
296 | |
297 | params.insert_path( |
298 | "adaptive_pixel_renderer.min_samples" , |
299 | g_cl.m_samples.values()[0]); |
300 | |
301 | params.insert_path( |
302 | "adaptive_pixel_renderer.max_samples" , |
303 | g_cl.m_samples.values()[1]); |
304 | } |
305 | } |
306 | |
307 | void apply_passes_command_line_option(ParamArray& params) |
308 | { |
309 | if (g_cl.m_passes.is_set()) |
310 | { |
311 | params.insert_path( |
312 | "generic_frame_renderer.passes" , |
313 | g_cl.m_passes.values()[0]); |
314 | } |
315 | } |
316 | |
317 | void apply_select_object_instances_command_line_option(Assembly& assembly, const RegExFilter& filter) |
318 | { |
319 | static const char* ColorName = "opaque_black-75AB13E8-D5A2-4D27-A64E-4FC41B55A272" ; |
320 | static const char* BlackSurfaceShaderName = "opaque_black_surface_shader-75AB13E8-D5A2-4D27-A64E-4FC41B55A272" ; |
321 | static const char* BlackMaterialName = "opaque_black_material-75AB13E8-D5A2-4D27-A64E-4FC41B55A272" ; |
322 | |
323 | ColorValueArray color_values; |
324 | color_values.push_back(0.0f); |
325 | |
326 | assembly.colors().insert( |
327 | ColorEntityFactory::create( |
328 | ColorName, |
329 | ParamArray() |
330 | .insert("color_space" , "linear_rgb" ), |
331 | color_values)); |
332 | |
333 | assembly.surface_shaders().insert( |
334 | ConstantSurfaceShaderFactory().create( |
335 | BlackSurfaceShaderName, |
336 | ParamArray() |
337 | .insert("color" , ColorName))); |
338 | |
339 | assembly.materials().insert( |
340 | GenericMaterialFactory().create( |
341 | BlackMaterialName, |
342 | ParamArray() |
343 | .insert("surface_shader" , BlackSurfaceShaderName))); |
344 | |
345 | for (each<ObjectInstanceContainer> i = assembly.object_instances(); i; ++i) |
346 | { |
347 | if (!filter.accepts(i->get_name())) |
348 | { |
349 | Object* object = i->find_object(); |
350 | const size_t slot_count = object->get_material_slot_count(); |
351 | |
352 | for (size_t s = 0; s < slot_count; ++s) |
353 | { |
354 | const char* slot = object->get_material_slot(s); |
355 | i->assign_material(slot, ObjectInstance::FrontSide, BlackMaterialName); |
356 | i->assign_material(slot, ObjectInstance::BackSide, BlackMaterialName); |
357 | } |
358 | } |
359 | } |
360 | |
361 | for (each<AssemblyContainer> i = assembly.assemblies(); i; ++i) |
362 | apply_select_object_instances_command_line_option(*i, filter); |
363 | } |
364 | |
365 | void apply_select_object_instances_command_line_option(Project& project, const char* regex) |
366 | { |
367 | const RegExFilter filter(regex, RegExFilter::CaseSensitive); |
368 | |
369 | Scene* scene = project.get_scene(); |
370 | |
371 | for (each<AssemblyContainer> i = scene->assemblies(); i; ++i) |
372 | apply_select_object_instances_command_line_option(*i, filter); |
373 | } |
374 | |
375 | void apply_parameter_command_line_options(ParamArray& params) |
376 | { |
377 | for (size_t i = 0; i < g_cl.m_params.values().size(); ++i) |
378 | { |
379 | // Retrieve the assignment string (of the form name=value). |
380 | const string& s = g_cl.m_params.values()[i]; |
381 | |
382 | // Retrieve the name and the value of the parameter. |
383 | const string::size_type equal_pos = s.find_first_of('='); |
384 | const string path = s.substr(0, equal_pos); |
385 | const string value = s.substr(equal_pos + 1); |
386 | |
387 | // Insert the parameter. |
388 | params.insert_path(path, value); |
389 | } |
390 | } |
391 | |
392 | void apply_command_line_options(Project& project, ParamArray& params) |
393 | { |
394 | // Apply --disable-autosave option. |
395 | if (g_cl.m_disable_autosave.is_set()) |
396 | params.insert_path("autosave" , false); |
397 | |
398 | // Apply --threads option. |
399 | if (g_cl.m_threads.is_set()) |
400 | { |
401 | params.insert_path( |
402 | "rendering_threads" , |
403 | g_cl.m_threads.value()); |
404 | } |
405 | |
406 | // Apply --resolution option. |
407 | apply_resolution_command_line_option(project); |
408 | |
409 | // Apply --window option. |
410 | apply_crop_window_command_line_option(project); |
411 | |
412 | // Apply --samples option. |
413 | apply_samples_command_line_option(params); |
414 | |
415 | // Apply --passes option. |
416 | apply_passes_command_line_option(params); |
417 | |
418 | // Apply --override-shading option. |
419 | if (g_cl.m_override_shading.is_set()) |
420 | { |
421 | params.insert_path( |
422 | "shading_engine.override_shading.mode" , |
423 | g_cl.m_override_shading.value()); |
424 | } |
425 | |
426 | // Apply --select-object-instances option. |
427 | if (g_cl.m_select_object_instances.is_set()) |
428 | { |
429 | apply_select_object_instances_command_line_option( |
430 | project, |
431 | g_cl.m_select_object_instances.value().c_str()); |
432 | } |
433 | |
434 | // Apply --parameter options. |
435 | apply_parameter_command_line_options(params); |
436 | } |
437 | |
438 | #if defined __APPLE__ || defined _WIN32 |
439 | |
440 | // Invoke a system command to open an image file. |
441 | void display_frame(const string& path) |
442 | { |
443 | const string quoted_path = "\"" + path + "\"" ; |
444 | |
445 | #if defined __APPLE__ |
446 | const string command = "open " + quoted_path; |
447 | #elif defined _WIN32 |
448 | const string command = quoted_path; |
449 | #endif |
450 | |
451 | LOG_DEBUG(g_logger, "executing '%s'" , command.c_str()); |
452 | std::system(command.c_str()); // needs std:: qualifier |
453 | } |
454 | |
455 | #endif |
456 | |
457 | auto_release_ptr<Project> load_project(const string& project_filepath) |
458 | { |
459 | // Construct the schema file path. |
460 | const bf::path schema_filepath = |
461 | bf::path(Application::get_root_path()) |
462 | / "schemas" |
463 | / "project.xsd" ; |
464 | |
465 | // Load the project from disk. |
466 | ProjectFileReader reader; |
467 | return |
468 | reader.read( |
469 | project_filepath.c_str(), |
470 | schema_filepath.string().c_str()); |
471 | } |
472 | |
473 | bool configure_project(Project& project, ParamArray& params) |
474 | { |
475 | // Retrieve the name of the configuration to use. |
476 | const string config_name = g_cl.m_configuration.is_set() |
477 | ? g_cl.m_configuration.value() |
478 | : "final" ; |
479 | |
480 | // Retrieve the configuration. |
481 | const Configuration* configuration = |
482 | project.configurations().get_by_name(config_name.c_str()); |
483 | if (configuration == 0) |
484 | { |
485 | LOG_ERROR( |
486 | g_logger, |
487 | "the configuration \"%s\" does not exist." , |
488 | config_name.c_str()); |
489 | return false; |
490 | } |
491 | |
492 | // Start with the parameters from the base configuration. |
493 | if (configuration->get_base()) |
494 | params = configuration->get_base()->get_parameters(); |
495 | |
496 | // Apply the application settings. |
497 | params.merge(g_settings); |
498 | |
499 | // Merge the parameters from the configuration. |
500 | params.merge(configuration->get_parameters()); |
501 | |
502 | // Apply the command line options. |
503 | apply_command_line_options(project, params); |
504 | |
505 | return true; |
506 | } |
507 | |
508 | bool is_progressive_render(const ParamArray& params) |
509 | { |
510 | const string value = params.get_required<string>("frame_renderer" , "generic" ); |
511 | return value == "progressive" ; |
512 | } |
513 | |
514 | bool render(const string& project_filename) |
515 | { |
516 | // Load the project. |
517 | auto_release_ptr<Project> project = load_project(project_filename); |
518 | if (project.get() == 0) |
519 | return false; |
520 | |
521 | // Retrieve the rendering parameters. |
522 | ParamArray params; |
523 | if (!configure_project(project.ref(), params)) |
524 | return false; |
525 | |
526 | // Create the tile callback factory. |
527 | auto_ptr<ITileCallbackFactory> tile_callback_factory; |
528 | if (g_cl.m_mplay_display.is_set()) |
529 | { |
530 | tile_callback_factory.reset( |
531 | new MPlayTileCallbackFactory( |
532 | project_filename.c_str(), |
533 | is_progressive_render(params), |
534 | g_logger)); |
535 | } |
536 | else if (g_cl.m_hrmanpipe_display.is_set()) |
537 | { |
538 | tile_callback_factory.reset( |
539 | new HRmanPipeTileCallbackFactory( |
540 | g_cl.m_hrmanpipe_display.value(), |
541 | is_progressive_render(params), |
542 | g_logger)); |
543 | } |
544 | else if (g_cl.m_output.is_set() && g_cl.m_continuous_saving.is_set()) |
545 | { |
546 | tile_callback_factory.reset( |
547 | new ContinuousSavingTileCallbackFactory( |
548 | g_cl.m_output.value().c_str(), |
549 | g_logger)); |
550 | } |
551 | else if (project->get_display() == 0) |
552 | { |
553 | // Create a default tile callback if needed. |
554 | if (params.get_optional<string>("frame_renderer" , "" ) != "progressive" ) |
555 | { |
556 | tile_callback_factory.reset( |
557 | new ProgressTileCallbackFactory(g_logger)); |
558 | } |
559 | } |
560 | |
561 | // Create the master renderer. |
562 | DefaultRendererController renderer_controller; |
563 | MasterRenderer renderer( |
564 | project.ref(), |
565 | params, |
566 | &renderer_controller, |
567 | tile_callback_factory.get()); |
568 | |
569 | // Render the frame. |
570 | LOG_INFO(g_logger, "rendering frame..." ); |
571 | Stopwatch<DefaultWallclockTimer> stopwatch; |
572 | if (params.get_optional<bool>("background_mode" , true)) |
573 | { |
574 | ProcessPriorityContext background_context(ProcessPriorityLow, &g_logger); |
575 | stopwatch.start(); |
576 | if (!renderer.render()) |
577 | return false; |
578 | stopwatch.measure(); |
579 | } |
580 | else |
581 | { |
582 | stopwatch.start(); |
583 | if (!renderer.render()) |
584 | return false; |
585 | stopwatch.measure(); |
586 | } |
587 | |
588 | // Print rendering time. |
589 | const double seconds = stopwatch.get_seconds(); |
590 | LOG_INFO( |
591 | g_logger, |
592 | "rendering finished in %s." , |
593 | pretty_time(seconds, 3).c_str()); |
594 | |
595 | // Archive the frame to disk. |
596 | char* archive_path = 0; |
597 | if (params.get_optional<bool>("autosave" , true)) |
598 | { |
599 | // Construct the path to the archive directory. |
600 | const bf::path autosave_path = |
601 | bf::path(Application::get_root_path()) |
602 | / "images" / "autosave" ; |
603 | |
604 | // Archive the frame to disk. |
605 | LOG_INFO(g_logger, "archiving frame to disk..." ); |
606 | project->get_frame()->archive( |
607 | autosave_path.string().c_str(), |
608 | &archive_path); |
609 | } |
610 | |
611 | // Write the frame to disk. |
612 | if (g_cl.m_output.is_set() && !g_cl.m_continuous_saving.is_set()) |
613 | { |
614 | LOG_INFO(g_logger, "writing frame to disk..." ); |
615 | project->get_frame()->write_main_image(g_cl.m_output.value().c_str()); |
616 | project->get_frame()->write_aov_images(g_cl.m_output.value().c_str()); |
617 | } |
618 | else |
619 | { |
620 | const Frame* frame = project->get_frame(); |
621 | const string output_filename = |
622 | frame->get_parameters().get_optional<string>("output_filename" ); |
623 | |
624 | if (!output_filename.empty()) |
625 | { |
626 | LOG_INFO(g_logger, "writing frame to disk..." ); |
627 | frame->write_main_image(output_filename.c_str()); |
628 | |
629 | if (frame->get_parameters().get_optional<bool>("output_aovs" , false)) |
630 | frame->write_aov_images(output_filename.c_str()); |
631 | } |
632 | } |
633 | |
634 | #if defined __APPLE__ || defined _WIN32 |
635 | |
636 | // Display the output image. |
637 | if (g_cl.m_display_output.is_set()) |
638 | { |
639 | if (g_cl.m_output.is_set()) |
640 | display_frame(g_cl.m_output.value().c_str()); |
641 | else if (archive_path) |
642 | display_frame(archive_path); |
643 | else LOG_WARNING(g_logger, "cannot display output when no output is specified and autosave is disabled." ); |
644 | } |
645 | |
646 | #endif |
647 | |
648 | // Deallocate the memory used by the path to the archived image. |
649 | free_string(archive_path); |
650 | |
651 | return true; |
652 | } |
653 | |
654 | bool benchmark_render(const string& project_filename) |
655 | { |
656 | // Configure our logger. |
657 | SaveLogFormatterConfig save_g_logger_config(g_logger); |
658 | g_logger.reset_all_formats(); |
659 | g_logger.set_format(LogMessage::Info, "{message}" ); |
660 | |
661 | // Configure the renderer's logger: mute all log messages except warnings and errors. |
662 | SaveLogFormatterConfig save_global_logger_config(global_logger()); |
663 | global_logger().set_all_formats(string()); |
664 | global_logger().reset_format(LogMessage::Warning); |
665 | global_logger().reset_format(LogMessage::Error); |
666 | global_logger().reset_format(LogMessage::Fatal); |
667 | |
668 | // Load the project. |
669 | auto_release_ptr<Project> project = load_project(project_filename); |
670 | if (project.get() == 0) |
671 | return false; |
672 | |
673 | // Figure out the rendering parameters. |
674 | ParamArray params; |
675 | if (!configure_project(project.ref(), params)) |
676 | return false; |
677 | |
678 | // Create the master renderer. |
679 | DefaultRendererController renderer_controller; |
680 | MasterRenderer renderer( |
681 | project.ref(), |
682 | params, |
683 | &renderer_controller); |
684 | |
685 | double total_time_seconds, render_time_seconds; |
686 | { |
687 | // Raise the process priority to reduce interruptions. |
688 | ProcessPriorityContext benchmark_context(ProcessPriorityHigh, &g_logger); |
689 | Stopwatch<DefaultWallclockTimer> stopwatch; |
690 | |
691 | // Render a first time. |
692 | stopwatch.start(); |
693 | if (!renderer.render()) |
694 | return false; |
695 | stopwatch.measure(); |
696 | total_time_seconds = stopwatch.get_seconds(); |
697 | |
698 | // Render a second time. |
699 | if (!renderer.render()) |
700 | return false; |
701 | stopwatch.measure(); |
702 | render_time_seconds = stopwatch.get_seconds() - total_time_seconds; |
703 | } |
704 | |
705 | // Write the frame to disk. |
706 | if (g_cl.m_output.is_set()) |
707 | { |
708 | const char* file_path = g_cl.m_output.value().c_str(); |
709 | project->get_frame()->write_main_image(file_path); |
710 | project->get_frame()->write_aov_images(file_path); |
711 | } |
712 | |
713 | // Print benchmark results. |
714 | LOG_INFO(g_logger, "result=success" ); |
715 | LOG_INFO(g_logger, "total_time=%.6f" , total_time_seconds); |
716 | LOG_INFO(g_logger, "setup_time=%.6f" , total_time_seconds - render_time_seconds); |
717 | LOG_INFO(g_logger, "render_time=%.6f" , render_time_seconds); |
718 | |
719 | return true; |
720 | } |
721 | } |
722 | |
723 | |
724 | // |
725 | // Entry point of appleseed.cli. |
726 | // |
727 | |
728 | int main(int argc, const char* argv[]) |
729 | { |
730 | start_memory_tracking(); |
731 | |
732 | // Make sure appleseed is correctly installed. |
733 | Application::check_installation(g_logger); |
734 | |
735 | // Parse the command line. |
736 | g_cl.parse(argc, argv, g_logger); |
737 | |
738 | // Load and apply settings from the settings file. |
739 | Application::load_settings("appleseed.cli.xml" , g_settings, g_logger); |
740 | g_logger.configure_from_settings(g_settings); |
741 | |
742 | // Apply command line arguments. |
743 | g_cl.apply(g_logger); |
744 | |
745 | // Configure the renderer's global logger. |
746 | // Must be done after settings have been loaded and the command line |
747 | // has been parsed, because these two operations may replace the log |
748 | // target of the global logger. |
749 | global_logger().initialize_from(g_logger); |
750 | |
751 | bool success = true; |
752 | |
753 | // Run unit tests. |
754 | if (g_cl.m_run_unit_tests.is_set()) |
755 | success = success && run_unit_tests(); |
756 | |
757 | // Run unit benchmarks. |
758 | if (g_cl.m_run_unit_benchmarks.is_set()) |
759 | run_unit_benchmarks(); |
760 | |
761 | // Render the specified project. |
762 | if (!g_cl.m_filename.values().empty()) |
763 | { |
764 | const string project_filename = g_cl.m_filename.value(); |
765 | |
766 | if (g_cl.m_benchmark_mode.is_set()) |
767 | success = success && benchmark_render(project_filename); |
768 | else success = success && render(project_filename); |
769 | } |
770 | |
771 | return success ? 0 : 1; |
772 | } |
773 | |