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
76using namespace appleseed::cli;
77using namespace appleseed::shared;
78using namespace foundation;
79using namespace renderer;
80using namespace std;
81namespace bf = boost::filesystem;
82
83namespace
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
728int 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