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