Merge branch 'blender-v2.92-release'
[blender.git] / intern / cycles / app / cycles_standalone.cpp
1 /*
2  * Copyright 2011-2013 Blender Foundation
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #include <stdio.h>
18
19 #include "device/device.h"
20 #include "render/buffers.h"
21 #include "render/camera.h"
22 #include "render/integrator.h"
23 #include "render/scene.h"
24 #include "render/session.h"
25
26 #include "util/util_args.h"
27 #include "util/util_foreach.h"
28 #include "util/util_function.h"
29 #include "util/util_image.h"
30 #include "util/util_logging.h"
31 #include "util/util_path.h"
32 #include "util/util_progress.h"
33 #include "util/util_string.h"
34 #include "util/util_time.h"
35 #include "util/util_transform.h"
36 #include "util/util_unique_ptr.h"
37 #include "util/util_version.h"
38
39 #ifdef WITH_CYCLES_STANDALONE_GUI
40 #  include "util/util_view.h"
41 #endif
42
43 #include "app/cycles_xml.h"
44
45 CCL_NAMESPACE_BEGIN
46
47 struct Options {
48   Session *session;
49   Scene *scene;
50   string filepath;
51   int width, height;
52   SceneParams scene_params;
53   SessionParams session_params;
54   bool quiet;
55   bool show_help, interactive, pause;
56   string output_path;
57 } options;
58
59 static void session_print(const string &str)
60 {
61   /* print with carriage return to overwrite previous */
62   printf("\r%s", str.c_str());
63
64   /* add spaces to overwrite longer previous print */
65   static int maxlen = 0;
66   int len = str.size();
67   maxlen = max(len, maxlen);
68
69   for (int i = len; i < maxlen; i++)
70     printf(" ");
71
72   /* flush because we don't write an end of line */
73   fflush(stdout);
74 }
75
76 static void session_print_status()
77 {
78   string status, substatus;
79
80   /* get status */
81   float progress = options.session->progress.get_progress();
82   options.session->progress.get_status(status, substatus);
83
84   if (substatus != "")
85     status += ": " + substatus;
86
87   /* print status */
88   status = string_printf("Progress %05.2f   %s", (double)progress * 100, status.c_str());
89   session_print(status);
90 }
91
92 static bool write_render(const uchar *pixels, int w, int h, int channels)
93 {
94   string msg = string_printf("Writing image %s", options.output_path.c_str());
95   session_print(msg);
96
97   unique_ptr<ImageOutput> out = unique_ptr<ImageOutput>(ImageOutput::create(options.output_path));
98   if (!out) {
99     return false;
100   }
101
102   ImageSpec spec(w, h, channels, TypeDesc::UINT8);
103   if (!out->open(options.output_path, spec)) {
104     return false;
105   }
106
107   /* conversion for different top/bottom convention */
108   out->write_image(
109       TypeDesc::UINT8, pixels + (h - 1) * w * channels, AutoStride, -w * channels, AutoStride);
110
111   out->close();
112
113   return true;
114 }
115
116 static BufferParams &session_buffer_params()
117 {
118   static BufferParams buffer_params;
119   buffer_params.width = options.width;
120   buffer_params.height = options.height;
121   buffer_params.full_width = options.width;
122   buffer_params.full_height = options.height;
123
124   return buffer_params;
125 }
126
127 static void scene_init()
128 {
129   options.scene = new Scene(options.scene_params, options.session->device);
130
131   /* Read XML */
132   xml_read_file(options.scene, options.filepath.c_str());
133
134   /* Camera width/height override? */
135   if (!(options.width == 0 || options.height == 0)) {
136     options.scene->camera->width = options.width;
137     options.scene->camera->height = options.height;
138   }
139   else {
140     options.width = options.scene->camera->width;
141     options.height = options.scene->camera->height;
142   }
143
144   /* Calculate Viewplane */
145   options.scene->camera->compute_auto_viewplane();
146 }
147
148 static void session_init()
149 {
150   options.session_params.write_render_cb = write_render;
151   options.session = new Session(options.session_params);
152
153   if (options.session_params.background && !options.quiet)
154     options.session->progress.set_update_callback(function_bind(&session_print_status));
155 #ifdef WITH_CYCLES_STANDALONE_GUI
156   else
157     options.session->progress.set_update_callback(function_bind(&view_redraw));
158 #endif
159
160   /* load scene */
161   scene_init();
162   options.session->scene = options.scene;
163
164   options.session->reset(session_buffer_params(), options.session_params.samples);
165   options.session->start();
166 }
167
168 static void session_exit()
169 {
170   if (options.session) {
171     delete options.session;
172     options.session = NULL;
173   }
174
175   if (options.session_params.background && !options.quiet) {
176     session_print("Finished Rendering.");
177     printf("\n");
178   }
179 }
180
181 #ifdef WITH_CYCLES_STANDALONE_GUI
182 static void display_info(Progress &progress)
183 {
184   static double latency = 0.0;
185   static double last = 0;
186   double elapsed = time_dt();
187   string str, interactive;
188
189   latency = (elapsed - last);
190   last = elapsed;
191
192   double total_time, sample_time;
193   string status, substatus;
194
195   progress.get_time(total_time, sample_time);
196   progress.get_status(status, substatus);
197   float progress_val = progress.get_progress();
198
199   if (substatus != "")
200     status += ": " + substatus;
201
202   interactive = options.interactive ? "On" : "Off";
203
204   str = string_printf(
205       "%s"
206       "        Time: %.2f"
207       "        Latency: %.4f"
208       "        Progress: %05.2f"
209       "        Average: %.4f"
210       "        Interactive: %s",
211       status.c_str(),
212       total_time,
213       latency,
214       (double)progress_val * 100,
215       sample_time,
216       interactive.c_str());
217
218   view_display_info(str.c_str());
219
220   if (options.show_help)
221     view_display_help();
222 }
223
224 static void display()
225 {
226   static DeviceDrawParams draw_params = DeviceDrawParams();
227
228   options.session->draw(session_buffer_params(), draw_params);
229
230   display_info(options.session->progress);
231 }
232
233 static void motion(int x, int y, int button)
234 {
235   if (options.interactive) {
236     Transform matrix = options.session->scene->camera->matrix;
237
238     /* Translate */
239     if (button == 0) {
240       float3 translate = make_float3(x * 0.01f, -(y * 0.01f), 0.0f);
241       matrix = matrix * transform_translate(translate);
242     }
243
244     /* Rotate */
245     else if (button == 2) {
246       float4 r1 = make_float4((float)x * 0.1f, 0.0f, 1.0f, 0.0f);
247       matrix = matrix * transform_rotate(DEG2RADF(r1.x), make_float3(r1.y, r1.z, r1.w));
248
249       float4 r2 = make_float4(y * 0.1f, 1.0f, 0.0f, 0.0f);
250       matrix = matrix * transform_rotate(DEG2RADF(r2.x), make_float3(r2.y, r2.z, r2.w));
251     }
252
253     /* Update and Reset */
254     options.session->scene->camera->matrix = matrix;
255     options.session->scene->camera->need_update = true;
256     options.session->scene->camera->need_device_update = true;
257
258     options.session->reset(session_buffer_params(), options.session_params.samples);
259   }
260 }
261
262 static void resize(int width, int height)
263 {
264   options.width = width;
265   options.height = height;
266
267   if (options.session) {
268     /* Update camera */
269     options.session->scene->camera->width = width;
270     options.session->scene->camera->height = height;
271     options.session->scene->camera->compute_auto_viewplane();
272     options.session->scene->camera->need_update = true;
273     options.session->scene->camera->need_device_update = true;
274
275     options.session->reset(session_buffer_params(), options.session_params.samples);
276   }
277 }
278
279 static void keyboard(unsigned char key)
280 {
281   /* Toggle help */
282   if (key == 'h')
283     options.show_help = !(options.show_help);
284
285   /* Reset */
286   else if (key == 'r')
287     options.session->reset(session_buffer_params(), options.session_params.samples);
288
289   /* Cancel */
290   else if (key == 27)  // escape
291     options.session->progress.set_cancel("Canceled");
292
293   /* Pause */
294   else if (key == 'p') {
295     options.pause = !options.pause;
296     options.session->set_pause(options.pause);
297   }
298
299   /* Interactive Mode */
300   else if (key == 'i')
301     options.interactive = !(options.interactive);
302
303   /* Navigation */
304   else if (options.interactive && (key == 'w' || key == 'a' || key == 's' || key == 'd')) {
305     Transform matrix = options.session->scene->camera->matrix;
306     float3 translate;
307
308     if (key == 'w')
309       translate = make_float3(0.0f, 0.0f, 0.1f);
310     else if (key == 's')
311       translate = make_float3(0.0f, 0.0f, -0.1f);
312     else if (key == 'a')
313       translate = make_float3(-0.1f, 0.0f, 0.0f);
314     else if (key == 'd')
315       translate = make_float3(0.1f, 0.0f, 0.0f);
316
317     matrix = matrix * transform_translate(translate);
318
319     /* Update and Reset */
320     options.session->scene->camera->matrix = matrix;
321     options.session->scene->camera->need_update = true;
322     options.session->scene->camera->need_device_update = true;
323
324     options.session->reset(session_buffer_params(), options.session_params.samples);
325   }
326
327   /* Set Max Bounces */
328   else if (options.interactive && (key == '0' || key == '1' || key == '2' || key == '3')) {
329     int bounce;
330     switch (key) {
331       case '0':
332         bounce = 0;
333         break;
334       case '1':
335         bounce = 1;
336         break;
337       case '2':
338         bounce = 2;
339         break;
340       case '3':
341         bounce = 3;
342         break;
343       default:
344         bounce = 0;
345         break;
346     }
347
348     options.session->scene->integrator->max_bounce = bounce;
349
350     /* Update and Reset */
351     options.session->scene->integrator->need_update = true;
352
353     options.session->reset(session_buffer_params(), options.session_params.samples);
354   }
355 }
356 #endif
357
358 static int files_parse(int argc, const char *argv[])
359 {
360   if (argc > 0)
361     options.filepath = argv[0];
362
363   return 0;
364 }
365
366 static void options_parse(int argc, const char **argv)
367 {
368   options.width = 0;
369   options.height = 0;
370   options.filepath = "";
371   options.session = NULL;
372   options.quiet = false;
373
374   /* device names */
375   string device_names = "";
376   string devicename = "CPU";
377   bool list = false;
378
379   /* List devices for which support is compiled in. */
380   vector<DeviceType> types = Device::available_types();
381   foreach (DeviceType type, types) {
382     if (device_names != "")
383       device_names += ", ";
384
385     device_names += Device::string_from_type(type);
386   }
387
388   /* shading system */
389   string ssname = "svm";
390
391   /* parse options */
392   ArgParse ap;
393   bool help = false, debug = false, version = false;
394   int verbosity = 1;
395
396   ap.options("Usage: cycles [options] file.xml",
397              "%*",
398              files_parse,
399              "",
400              "--device %s",
401              &devicename,
402              ("Devices to use: " + device_names).c_str(),
403 #ifdef WITH_OSL
404              "--shadingsys %s",
405              &ssname,
406              "Shading system to use: svm, osl",
407 #endif
408              "--background",
409              &options.session_params.background,
410              "Render in background, without user interface",
411              "--quiet",
412              &options.quiet,
413              "In background mode, don't print progress messages",
414              "--samples %d",
415              &options.session_params.samples,
416              "Number of samples to render",
417              "--output %s",
418              &options.output_path,
419              "File path to write output image",
420              "--threads %d",
421              &options.session_params.threads,
422              "CPU Rendering Threads",
423              "--width  %d",
424              &options.width,
425              "Window width in pixel",
426              "--height %d",
427              &options.height,
428              "Window height in pixel",
429              "--tile-width %d",
430              &options.session_params.tile_size.x,
431              "Tile width in pixels",
432              "--tile-height %d",
433              &options.session_params.tile_size.y,
434              "Tile height in pixels",
435              "--list-devices",
436              &list,
437              "List information about all available devices",
438 #ifdef WITH_CYCLES_LOGGING
439              "--debug",
440              &debug,
441              "Enable debug logging",
442              "--verbose %d",
443              &verbosity,
444              "Set verbosity of the logger",
445 #endif
446              "--help",
447              &help,
448              "Print help message",
449              "--version",
450              &version,
451              "Print version number",
452              NULL);
453
454   if (ap.parse(argc, argv) < 0) {
455     fprintf(stderr, "%s\n", ap.geterror().c_str());
456     ap.usage();
457     exit(EXIT_FAILURE);
458   }
459
460   if (debug) {
461     util_logging_start();
462     util_logging_verbosity_set(verbosity);
463   }
464
465   if (list) {
466     vector<DeviceInfo> devices = Device::available_devices();
467     printf("Devices:\n");
468
469     foreach (DeviceInfo &info, devices) {
470       printf("    %-10s%s%s\n",
471              Device::string_from_type(info.type).c_str(),
472              info.description.c_str(),
473              (info.display_device) ? " (display)" : "");
474     }
475
476     exit(EXIT_SUCCESS);
477   }
478   else if (version) {
479     printf("%s\n", CYCLES_VERSION_STRING);
480     exit(EXIT_SUCCESS);
481   }
482   else if (help || options.filepath == "") {
483     ap.usage();
484     exit(EXIT_SUCCESS);
485   }
486
487   if (ssname == "osl")
488     options.scene_params.shadingsystem = SHADINGSYSTEM_OSL;
489   else if (ssname == "svm")
490     options.scene_params.shadingsystem = SHADINGSYSTEM_SVM;
491
492 #ifndef WITH_CYCLES_STANDALONE_GUI
493   options.session_params.background = true;
494 #endif
495
496   /* Use progressive rendering */
497   options.session_params.progressive = true;
498
499   /* find matching device */
500   DeviceType device_type = Device::type_from_string(devicename.c_str());
501   vector<DeviceInfo> devices = Device::available_devices(DEVICE_MASK(device_type));
502
503   bool device_available = false;
504   if (!devices.empty()) {
505     options.session_params.device = devices.front();
506     device_available = true;
507   }
508
509   /* handle invalid configurations */
510   if (options.session_params.device.type == DEVICE_NONE || !device_available) {
511     fprintf(stderr, "Unknown device: %s\n", devicename.c_str());
512     exit(EXIT_FAILURE);
513   }
514 #ifdef WITH_OSL
515   else if (!(ssname == "osl" || ssname == "svm")) {
516     fprintf(stderr, "Unknown shading system: %s\n", ssname.c_str());
517     exit(EXIT_FAILURE);
518   }
519   else if (options.scene_params.shadingsystem == SHADINGSYSTEM_OSL &&
520            options.session_params.device.type != DEVICE_CPU) {
521     fprintf(stderr, "OSL shading system only works with CPU device\n");
522     exit(EXIT_FAILURE);
523   }
524 #endif
525   else if (options.session_params.samples < 0) {
526     fprintf(stderr, "Invalid number of samples: %d\n", options.session_params.samples);
527     exit(EXIT_FAILURE);
528   }
529   else if (options.filepath == "") {
530     fprintf(stderr, "No file path specified\n");
531     exit(EXIT_FAILURE);
532   }
533
534   /* For smoother Viewport */
535   options.session_params.start_resolution = 64;
536 }
537
538 CCL_NAMESPACE_END
539
540 using namespace ccl;
541
542 int main(int argc, const char **argv)
543 {
544   util_logging_init(argv[0]);
545   path_init();
546   options_parse(argc, argv);
547
548 #ifdef WITH_CYCLES_STANDALONE_GUI
549   if (options.session_params.background) {
550 #endif
551     session_init();
552     options.session->wait();
553     session_exit();
554 #ifdef WITH_CYCLES_STANDALONE_GUI
555   }
556   else {
557     string title = "Cycles: " + path_filename(options.filepath);
558
559     /* init/exit are callback so they run while GL is initialized */
560     view_main_loop(title.c_str(),
561                    options.width,
562                    options.height,
563                    session_init,
564                    session_exit,
565                    resize,
566                    display,
567                    keyboard,
568                    motion);
569   }
570 #endif
571
572   return 0;
573 }