svn merge ^/trunk/blender -r42920:42927
[blender.git] / source / blender / editors / render / render_internal.c
1 /*
2  * ***** BEGIN GPL LICENSE BLOCK *****
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software Foundation,
16  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  *
18  * The Original Code is Copyright (C) 2008 Blender Foundation.
19  * All rights reserved.
20  *
21  *
22  * ***** END GPL LICENSE BLOCK *****
23  */
24
25 /** \file blender/editors/render/render_internal.c
26  *  \ingroup edrend
27  */
28
29
30 #include <math.h>
31 #include <string.h>
32 #include <stddef.h>
33
34 #include "MEM_guardedalloc.h"
35
36 #include "BLI_blenlib.h"
37 #include "BLI_math.h"
38 #include "BLI_threads.h"
39 #include "BLI_rand.h"
40 #include "BLI_utildefines.h"
41
42 #include "DNA_scene_types.h"
43
44 #include "BKE_blender.h"
45 #include "BKE_context.h"
46 #include "BKE_global.h"
47 #include "BKE_image.h"
48 #include "BKE_library.h"
49 #include "BKE_main.h"
50 #include "BKE_node.h"
51 #include "BKE_multires.h"
52 #include "BKE_report.h"
53 #include "BKE_sequencer.h"
54 #include "BKE_screen.h"
55 #include "BKE_scene.h"
56
57 #include "WM_api.h"
58 #include "WM_types.h"
59
60 #include "ED_screen.h"
61 #include "ED_object.h"
62
63 #include "RE_pipeline.h"
64 #include "IMB_imbuf.h"
65 #include "IMB_imbuf_types.h"
66
67 #include "RNA_access.h"
68 #include "RNA_define.h"
69
70 #include "wm_window.h"
71
72 #include "render_intern.h"
73
74 /* Render Callbacks */
75
76 /* called inside thread! */
77 void image_buffer_rect_update(Scene *scene, RenderResult *rr, ImBuf *ibuf, volatile rcti *renrect)
78 {
79         float x1, y1, *rectf= NULL;
80         int ymin, ymax, xmin, xmax;
81         int rymin, rxmin, do_color_management;
82         char *rectc;
83
84         /* if renrect argument, we only refresh scanlines */
85         if(renrect) {
86                 /* if ymax==recty, rendering of layer is ready, we should not draw, other things happen... */
87                 if(rr->renlay==NULL || renrect->ymax>=rr->recty)
88                         return;
89
90                 /* xmin here is first subrect x coord, xmax defines subrect width */
91                 xmin = renrect->xmin + rr->crop;
92                 xmax = renrect->xmax - xmin + rr->crop;
93                 if(xmax<2)
94                         return;
95
96                 ymin= renrect->ymin + rr->crop;
97                 ymax= renrect->ymax - ymin + rr->crop;
98                 if(ymax<2)
99                         return;
100                 renrect->ymin= renrect->ymax;
101
102         }
103         else {
104                 xmin = ymin = rr->crop;
105                 xmax = rr->rectx - 2*rr->crop;
106                 ymax = rr->recty - 2*rr->crop;
107         }
108
109         /* xmin ymin is in tile coords. transform to ibuf */
110         rxmin= rr->tilerect.xmin + xmin;
111         if(rxmin >= ibuf->x) return;
112         rymin= rr->tilerect.ymin + ymin;
113         if(rymin >= ibuf->y) return;
114
115         if(rxmin + xmax > ibuf->x)
116                 xmax= ibuf->x - rxmin;
117         if(rymin + ymax > ibuf->y)
118                 ymax= ibuf->y - rymin;
119
120         if(xmax < 1 || ymax < 1) return;
121
122         /* find current float rect for display, first case is after composit... still weak */
123         if(rr->rectf)
124                 rectf= rr->rectf;
125         else {
126                 if(rr->rect32)
127                         return;
128                 else {
129                         if(rr->renlay==NULL || rr->renlay->rectf==NULL) return;
130                         rectf= rr->renlay->rectf;
131                 }
132         }
133         if(rectf==NULL) return;
134
135         if(ibuf->rect==NULL)
136                 imb_addrectImBuf(ibuf);
137         
138         rectf+= 4*(rr->rectx*ymin + xmin);
139         rectc= (char *)(ibuf->rect + ibuf->x*rymin + rxmin);
140
141         do_color_management = (scene && (scene->r.color_mgt_flag & R_COLOR_MANAGEMENT));
142         
143         /* XXX make nice consistent functions for this */
144         for(y1= 0; y1<ymax; y1++) {
145                 float *rf= rectf;
146                 float srgb[3];
147                 char *rc= rectc;
148                 const float dither = ibuf->dither / 255.0f;
149
150                 /* XXX temp. because crop offset */
151                 if(rectc >= (char *)(ibuf->rect)) {
152                         for(x1= 0; x1<xmax; x1++, rf += 4, rc+=4) {
153                                 /* color management */
154                                 if(do_color_management) {
155                                         srgb[0]= linearrgb_to_srgb(rf[0]);
156                                         srgb[1]= linearrgb_to_srgb(rf[1]);
157                                         srgb[2]= linearrgb_to_srgb(rf[2]);
158                                 }
159                                 else {
160                                         copy_v3_v3(srgb, rf);
161                                 }
162
163                                 /* dither */
164                                 if(dither != 0.0f) {
165                                         const float d = (BLI_frand()-0.5f)*dither;
166
167                                         srgb[0] += d;
168                                         srgb[1] += d;
169                                         srgb[2] += d;
170                                 }
171
172                                 /* write */
173                                 rc[0]= FTOCHAR(srgb[0]);
174                                 rc[1]= FTOCHAR(srgb[1]);
175                                 rc[2]= FTOCHAR(srgb[2]);
176                                 rc[3]= FTOCHAR(rf[3]);
177                         }
178                 }
179
180                 rectf += 4*rr->rectx;
181                 rectc += 4*ibuf->x;
182         }
183 }
184
185 /* ****************************** render invoking ***************** */
186
187 /* set callbacks, exported to sequence render too.
188  Only call in foreground (UI) renders. */
189
190 static void screen_render_scene_layer_set(wmOperator *op, Main *mainp, Scene **scene, SceneRenderLayer **srl)
191 {
192         /* single layer re-render */
193         if(RNA_property_is_set(op->ptr, "scene")) {
194                 Scene *scn;
195                 char scene_name[MAX_ID_NAME-2];
196
197                 RNA_string_get(op->ptr, "scene", scene_name);
198                 scn = (Scene *)BLI_findstring(&mainp->scene, scene_name, offsetof(ID, name) + 2);
199                 
200                 if (scn) {
201                         /* camera switch wont have updated */
202                         scn->r.cfra= (*scene)->r.cfra;
203                         scene_camera_switch_update(scn);
204
205                         *scene = scn;
206                 }
207         }
208
209         if(RNA_property_is_set(op->ptr, "layer")) {
210                 SceneRenderLayer *rl;
211                 char rl_name[RE_MAXNAME];
212
213                 RNA_string_get(op->ptr, "layer", rl_name);
214                 rl = (SceneRenderLayer *)BLI_findstring(&(*scene)->r.layers, rl_name, offsetof(SceneRenderLayer, name));
215                 
216                 if (rl)
217                         *srl = rl;
218         }
219 }
220
221 /* executes blocking render */
222 static int screen_render_exec(bContext *C, wmOperator *op)
223 {
224         Scene *scene= CTX_data_scene(C);
225         SceneRenderLayer *srl= NULL;
226         Render *re;
227         Image *ima;
228         View3D *v3d= CTX_wm_view3d(C);
229         Main *mainp= CTX_data_main(C);
230         unsigned int lay;
231         const short is_animation= RNA_boolean_get(op->ptr, "animation");
232         const short is_write_still= RNA_boolean_get(op->ptr, "write_still");
233         struct Object *camera_override= v3d ? V3D_CAMERA_LOCAL(v3d) : NULL;
234
235         /* custom scene and single layer re-render */
236         screen_render_scene_layer_set(op, mainp, &scene, &srl);
237
238         if(!is_animation && is_write_still && BKE_imtype_is_movie(scene->r.im_format.imtype)) {
239                 BKE_report(op->reports, RPT_ERROR, "Can't write a single file with an animation format selected");
240                 return OPERATOR_CANCELLED;
241         }
242
243         re= RE_NewRender(scene->id.name);
244         lay= (v3d)? v3d->lay: scene->lay;
245
246         G.afbreek= 0;
247         RE_test_break_cb(re, NULL, (int (*)(void *)) blender_test_break);
248
249         ima= BKE_image_verify_viewer(IMA_TYPE_R_RESULT, "Render Result");
250         BKE_image_signal(ima, NULL, IMA_SIGNAL_FREE);
251         BKE_image_backup_render(scene, ima);
252
253         /* cleanup sequencer caches before starting user triggered render.
254            otherwise, invalidated cache entries can make their way into
255            the output rendering. We can't put that into RE_BlenderFrame,
256            since sequence rendering can call that recursively... (peter) */
257         seq_stripelem_cache_cleanup();
258
259         RE_SetReports(re, op->reports);
260
261         if(is_animation)
262                 RE_BlenderAnim(re, mainp, scene, camera_override, lay, scene->r.sfra, scene->r.efra, scene->r.frame_step);
263         else
264                 RE_BlenderFrame(re, mainp, scene, srl, camera_override, lay, scene->r.cfra, is_write_still);
265
266         RE_SetReports(re, NULL);
267
268         // no redraw needed, we leave state as we entered it
269         ED_update_for_newframe(mainp, scene, CTX_wm_screen(C), 1);
270
271         WM_event_add_notifier(C, NC_SCENE|ND_RENDER_RESULT, scene);
272
273         return OPERATOR_FINISHED;
274 }
275
276 typedef struct RenderJob {
277         Main *main;
278         Scene *scene;
279         Render *re;
280         wmWindow *win;
281         SceneRenderLayer *srl;
282         struct Object *camera_override;
283         int lay;
284         short anim, write_still;
285         Image *image;
286         ImageUser iuser;
287         short *stop;
288         short *do_update;
289         float *progress;
290         ReportList *reports;
291 } RenderJob;
292
293 static void render_freejob(void *rjv)
294 {
295         RenderJob *rj= rjv;
296
297         MEM_freeN(rj);
298 }
299
300 /* str is IMA_MAX_RENDER_TEXT in size */
301 static void make_renderinfo_string(RenderStats *rs, Scene *scene, char *str)
302 {
303         char info_time_str[32]; // used to be extern to header_info.c
304         uintptr_t mem_in_use, mmap_in_use, peak_memory;
305         float megs_used_memory, mmap_used_memory, megs_peak_memory;
306         char *spos= str;
307
308         mem_in_use= MEM_get_memory_in_use();
309         mmap_in_use= MEM_get_mapped_memory_in_use();
310         peak_memory = MEM_get_peak_memory();
311
312         megs_used_memory= (mem_in_use-mmap_in_use)/(1024.0*1024.0);
313         mmap_used_memory= (mmap_in_use)/(1024.0*1024.0);
314         megs_peak_memory = (peak_memory)/(1024.0*1024.0);
315
316         if(scene->lay & 0xFF000000)
317                 spos+= sprintf(spos, "Localview | ");
318         else if(scene->r.scemode & R_SINGLE_LAYER)
319                 spos+= sprintf(spos, "Single Layer | ");
320
321         if(rs->statstr) {
322                 spos+= sprintf(spos, "%s ", rs->statstr);
323         }
324         else {
325                 spos+= sprintf(spos, "Fra:%d  ", (scene->r.cfra));
326                 if(rs->totvert) spos+= sprintf(spos, "Ve:%d ", rs->totvert);
327                 if(rs->totface) spos+= sprintf(spos, "Fa:%d ", rs->totface);
328                 if(rs->tothalo) spos+= sprintf(spos, "Ha:%d ", rs->tothalo);
329                 if(rs->totstrand) spos+= sprintf(spos, "St:%d ", rs->totstrand);
330                 if(rs->totlamp) spos+= sprintf(spos, "La:%d ", rs->totlamp);
331                 spos+= sprintf(spos, "Mem:%.2fM (%.2fM, peak %.2fM) ", megs_used_memory, mmap_used_memory, megs_peak_memory);
332
333                 if(rs->curfield)
334                         spos+= sprintf(spos, "Field %d ", rs->curfield);
335                 if(rs->curblur)
336                         spos+= sprintf(spos, "Blur %d ", rs->curblur);
337         }
338
339         BLI_timestr(rs->lastframetime, info_time_str);
340         spos+= sprintf(spos, "Time:%s ", info_time_str);
341
342         if(rs->curfsa)
343                 spos+= sprintf(spos, "| Full Sample %d ", rs->curfsa);
344         
345         if(rs->infostr && rs->infostr[0])
346                 spos+= sprintf(spos, "| %s ", rs->infostr);
347
348         /* very weak... but 512 characters is quite safe */
349         if(spos >= str+IMA_MAX_RENDER_TEXT)
350                 if (G.f & G_DEBUG)
351                         printf("WARNING! renderwin text beyond limit \n");
352
353 }
354
355 static void image_renderinfo_cb(void *rjv, RenderStats *rs)
356 {
357         RenderJob *rj= rjv;
358         RenderResult *rr;
359
360         rr= RE_AcquireResultRead(rj->re);
361
362         if(rr) {
363                 /* malloc OK here, stats_draw is not in tile threads */
364                 if(rr->text==NULL)
365                         rr->text= MEM_callocN(IMA_MAX_RENDER_TEXT, "rendertext");
366
367                 make_renderinfo_string(rs, rj->scene, rr->text);
368         }
369
370         RE_ReleaseResult(rj->re);
371
372         /* make jobs timer to send notifier */
373         *(rj->do_update)= 1;
374
375 }
376
377 static void render_progress_update(void *rjv, float progress)
378 {
379         RenderJob *rj= rjv;
380         
381         if(rj->progress && *rj->progress != progress) {
382                 *rj->progress = progress;
383
384                 /* make jobs timer to send notifier */
385                 *(rj->do_update)= 1;
386         }
387 }
388
389 static void image_rect_update(void *rjv, RenderResult *rr, volatile rcti *renrect)
390 {
391         RenderJob *rj= rjv;
392         Image *ima= rj->image;
393         ImBuf *ibuf;
394         void *lock;
395
396         /* only update if we are displaying the slot being rendered */
397         if(ima->render_slot != ima->last_render_slot)
398                 return;
399
400         ibuf= BKE_image_acquire_ibuf(ima, &rj->iuser, &lock);
401         if(ibuf) {
402                 image_buffer_rect_update(rj->scene, rr, ibuf, renrect);
403
404                 /* make jobs timer to send notifier */
405                 *(rj->do_update)= 1;
406         }
407         BKE_image_release_ibuf(ima, lock);
408 }
409
410 static void render_startjob(void *rjv, short *stop, short *do_update, float *progress)
411 {
412         RenderJob *rj= rjv;
413
414         rj->stop= stop;
415         rj->do_update= do_update;
416         rj->progress= progress;
417
418         RE_SetReports(rj->re, rj->reports);
419
420         if(rj->anim)
421                 RE_BlenderAnim(rj->re, rj->main, rj->scene, rj->camera_override, rj->lay, rj->scene->r.sfra, rj->scene->r.efra, rj->scene->r.frame_step);
422         else
423                 RE_BlenderFrame(rj->re, rj->main, rj->scene, rj->srl, rj->camera_override, rj->lay, rj->scene->r.cfra, rj->write_still);
424
425         RE_SetReports(rj->re, NULL);
426 }
427
428 static void render_endjob(void *rjv)
429 {
430         RenderJob *rj= rjv;     
431
432         /* this render may be used again by the sequencer without the active 'Render' where the callbacks
433          * would be re-assigned. assign dummy callbacks to avoid referencing freed renderjobs bug [#24508] */
434         RE_InitRenderCB(rj->re);
435
436         if(rj->main != G.main)
437                 free_main(rj->main);
438
439         /* else the frame will not update for the original value */
440         if(!(rj->scene->r.scemode & R_NO_FRAME_UPDATE))
441                 ED_update_for_newframe(G.main, rj->scene, rj->win->screen, 1);
442         
443         /* XXX above function sets all tags in nodes */
444         ntreeCompositClearTags(rj->scene->nodetree);
445         
446         /* potentially set by caller */
447         rj->scene->r.scemode &= ~R_NO_FRAME_UPDATE;
448         
449         if(rj->srl) {
450                 nodeUpdateID(rj->scene->nodetree, &rj->scene->id);
451                 WM_main_add_notifier(NC_NODE|NA_EDITED, rj->scene);
452         }
453         
454         /* XXX render stability hack */
455         G.rendering = 0;
456         WM_main_add_notifier(NC_WINDOW, NULL);
457 }
458
459 /* called by render, check job 'stop' value or the global */
460 static int render_breakjob(void *rjv)
461 {
462         RenderJob *rj= rjv;
463
464         if(G.afbreek)
465                 return 1;
466         if(rj->stop && *(rj->stop))
467                 return 1;
468         return 0;
469 }
470
471 /* runs in thread, no cursor setting here works. careful with notifiers too (malloc conflicts) */
472 /* maybe need a way to get job send notifer? */
473 static void render_drawlock(void *UNUSED(rjv), int lock)
474 {
475         BKE_spacedata_draw_locks(lock);
476         
477 }
478
479 /* catch esc */
480 static int screen_render_modal(bContext *C, wmOperator *UNUSED(op), wmEvent *event)
481 {
482         /* no running blender, remove handler and pass through */
483         if(0==WM_jobs_test(CTX_wm_manager(C), CTX_data_scene(C))) {
484                 return OPERATOR_FINISHED|OPERATOR_PASS_THROUGH;
485         }
486
487         /* running render */
488         switch (event->type) {
489                 case ESCKEY:
490                         return OPERATOR_RUNNING_MODAL;
491                         break;
492         }
493         return OPERATOR_PASS_THROUGH;
494 }
495
496 /* using context, starts job */
497 static int screen_render_invoke(bContext *C, wmOperator *op, wmEvent *event)
498 {
499         /* new render clears all callbacks */
500         Main *mainp;
501         Scene *scene= CTX_data_scene(C);
502         SceneRenderLayer *srl=NULL;
503         bScreen *screen= CTX_wm_screen(C);
504         View3D *v3d= CTX_wm_view3d(C);
505         Render *re;
506         wmJob *steve;
507         RenderJob *rj;
508         Image *ima;
509         int jobflag;
510         const short is_animation= RNA_boolean_get(op->ptr, "animation");
511         const short is_write_still= RNA_boolean_get(op->ptr, "write_still");
512         struct Object *camera_override= v3d ? V3D_CAMERA_LOCAL(v3d) : NULL;
513         const char *name;
514         
515         /* only one render job at a time */
516         if(WM_jobs_test(CTX_wm_manager(C), scene))
517                 return OPERATOR_CANCELLED;
518
519         if(!RE_is_rendering_allowed(scene, camera_override, op->reports)) {
520                 return OPERATOR_CANCELLED;
521         }
522
523         if(!is_animation && is_write_still && BKE_imtype_is_movie(scene->r.im_format.imtype)) {
524                 BKE_report(op->reports, RPT_ERROR, "Can't write a single file with an animation format selected");
525                 return OPERATOR_CANCELLED;
526         }       
527         
528         /* stop all running jobs, currently previews frustrate Render */
529         WM_jobs_stop_all(CTX_wm_manager(C));
530
531         /* get main */
532         if(G.rt == 101) {
533                 /* thread-safety experiment, copy main from the undo buffer */
534                 mainp= BKE_undo_get_main(&scene);
535         }
536         else
537                 mainp= CTX_data_main(C);
538
539         /* cancel animation playback */
540         if (screen->animtimer)
541                 ED_screen_animation_play(C, 0, 0);
542         
543         /* handle UI stuff */
544         WM_cursor_wait(1);
545
546         /* flush multires changes (for sculpt) */
547         multires_force_render_update(CTX_data_active_object(C));
548
549         /* cleanup sequencer caches before starting user triggered render.
550            otherwise, invalidated cache entries can make their way into
551            the output rendering. We can't put that into RE_BlenderFrame,
552            since sequence rendering can call that recursively... (peter) */
553         seq_stripelem_cache_cleanup();
554
555         /* get editmode results */
556         ED_object_exit_editmode(C, 0);  /* 0 = does not exit editmode */
557
558         // store spare
559         // get view3d layer, local layer, make this nice api call to render
560         // store spare
561
562         /* ensure at least 1 area shows result */
563         render_view_open(C, event->x, event->y);
564
565         jobflag= WM_JOB_EXCL_RENDER|WM_JOB_PRIORITY|WM_JOB_PROGRESS;
566         
567         /* custom scene and single layer re-render */
568         screen_render_scene_layer_set(op, mainp, &scene, &srl);
569
570         if(RNA_property_is_set(op->ptr, "layer"))
571                 jobflag |= WM_JOB_SUSPEND;
572
573         /* job custom data */
574         rj= MEM_callocN(sizeof(RenderJob), "render job");
575         rj->main= mainp;
576         rj->scene= scene;
577         rj->win= CTX_wm_window(C);
578         rj->srl = srl;
579         rj->camera_override = camera_override;
580         rj->lay = (v3d)? v3d->lay: scene->lay;
581         rj->anim= is_animation;
582         rj->write_still= is_write_still && !is_animation;
583         rj->iuser.scene= scene;
584         rj->iuser.ok= 1;
585         rj->reports= op->reports;
586
587         /* setup job */
588         if(RE_seq_render_active(scene, &scene->r)) name= "Sequence Render";
589         else name= "Render";
590
591         steve= WM_jobs_get(CTX_wm_manager(C), CTX_wm_window(C), scene, name, jobflag);
592         WM_jobs_customdata(steve, rj, render_freejob);
593         WM_jobs_timer(steve, 0.2, NC_SCENE|ND_RENDER_RESULT, 0);
594         WM_jobs_callbacks(steve, render_startjob, NULL, NULL, render_endjob);
595
596         /* get a render result image, and make sure it is empty */
597         ima= BKE_image_verify_viewer(IMA_TYPE_R_RESULT, "Render Result");
598         BKE_image_signal(ima, NULL, IMA_SIGNAL_FREE);
599         BKE_image_backup_render(rj->scene, ima);
600         rj->image= ima;
601
602         /* setup new render */
603         re= RE_NewRender(scene->id.name);
604         RE_test_break_cb(re, rj, render_breakjob);
605         RE_draw_lock_cb(re, rj, render_drawlock);
606         RE_display_draw_cb(re, rj, image_rect_update);
607         RE_stats_draw_cb(re, rj, image_renderinfo_cb);
608         RE_progress_cb(re, rj, render_progress_update);
609
610         rj->re= re;
611         G.afbreek= 0;
612
613         WM_jobs_start(CTX_wm_manager(C), steve);
614
615         WM_cursor_wait(0);
616         WM_event_add_notifier(C, NC_SCENE|ND_RENDER_RESULT, scene);
617
618         /* we set G.rendering here already instead of only in the job, this ensure
619            main loop or other scene updates are disabled in time, since they may
620            have started before the job thread */
621         G.rendering = 1;
622
623         /* add modal handler for ESC */
624         WM_event_add_modal_handler(C, op);
625
626         return OPERATOR_RUNNING_MODAL;
627 }
628
629 /* contextual render, using current scene, view3d? */
630 void RENDER_OT_render(wmOperatorType *ot)
631 {
632         /* identifiers */
633         ot->name= "Render";
634         ot->description= "Render active scene";
635         ot->idname= "RENDER_OT_render";
636
637         /* api callbacks */
638         ot->invoke= screen_render_invoke;
639         ot->modal= screen_render_modal;
640         ot->exec= screen_render_exec;
641
642         /*ot->poll= ED_operator_screenactive;*/ /* this isnt needed, causes failer in background mode */
643
644         RNA_def_boolean(ot->srna, "animation", 0, "Animation", "Render files from the animation range of this scene");
645         RNA_def_boolean(ot->srna, "write_still", 0, "Write Image", "Save rendered the image to the output path (used only when animation is disabled)");
646         RNA_def_string(ot->srna, "layer", "", RE_MAXNAME, "Render Layer", "Single render layer to re-render (used only when animation is disabled)");
647         RNA_def_string(ot->srna, "scene", "", MAX_ID_NAME-2, "Scene", "Scene to render, current scene if not specified");
648 }
649