style cleanup: some warnigs & spelling.
[blender.git] / intern / cycles / render / session.cpp
1 /*
2  * Copyright 2011, Blender Foundation.
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
19 #include <string.h>
20 #include <limits.h>
21
22 #include "buffers.h"
23 #include "camera.h"
24 #include "device.h"
25 #include "scene.h"
26 #include "session.h"
27
28 #include "util_foreach.h"
29 #include "util_function.h"
30 #include "util_math.h"
31 #include "util_opengl.h"
32 #include "util_task.h"
33 #include "util_time.h"
34
35 CCL_NAMESPACE_BEGIN
36
37 /* Note about  preserve_tile_device option for tile manager:
38  * progressive refine and viewport rendering does requires tiles to
39  * always be allocated for the same device
40  */
41 Session::Session(const SessionParams& params_)
42 : params(params_),
43   tile_manager(params.progressive, params.samples, params.tile_size, params.start_resolution,
44        params.background == false || params.progressive_refine, params.background, params.tile_order,
45        max(params.device.multi_devices.size(), 1)),
46   stats()
47 {
48         device_use_gl = ((params.device.type != DEVICE_CPU) && !params.background);
49
50         TaskScheduler::init(params.threads);
51
52         device = Device::create(params.device, stats, params.background);
53
54         if(params.background) {
55                 buffers = NULL;
56                 display = NULL;
57         }
58         else {
59                 buffers = new RenderBuffers(device);
60                 display = new DisplayBuffer(device);
61         }
62
63         session_thread = NULL;
64         scene = NULL;
65
66         start_time = 0.0;
67         reset_time = 0.0;
68         preview_time = 0.0;
69         paused_time = 0.0;
70
71         delayed_reset.do_reset = false;
72         delayed_reset.samples = 0;
73
74         display_outdated = false;
75         gpu_draw_ready = false;
76         gpu_need_tonemap = false;
77         pause = false;
78         kernels_loaded = false;
79 }
80
81 Session::~Session()
82 {
83         if(session_thread) {
84                 progress.set_cancel("Exiting");
85
86                 gpu_need_tonemap = false;
87                 gpu_need_tonemap_cond.notify_all();
88
89                 {
90                         thread_scoped_lock pause_lock(pause_mutex);
91                         pause = false;
92                 }
93                 pause_cond.notify_all();
94
95                 wait();
96         }
97
98         if(display && params.output_path != "") {
99                 tonemap();
100
101                 progress.set_status("Writing Image", params.output_path);
102                 display->write(device, params.output_path);
103         }
104
105         foreach(RenderBuffers *buffers, tile_buffers)
106                 delete buffers;
107
108         delete buffers;
109         delete display;
110         delete scene;
111         delete device;
112
113         TaskScheduler::exit();
114 }
115
116 void Session::start()
117 {
118         session_thread = new thread(function_bind(&Session::run, this));
119 }
120
121 bool Session::ready_to_reset()
122 {
123         double dt = time_dt() - reset_time;
124
125         if(!display_outdated)
126                 return (dt > params.reset_timeout);
127         else
128                 return (dt > params.cancel_timeout);
129 }
130
131 /* GPU Session */
132
133 void Session::reset_gpu(BufferParams& buffer_params, int samples)
134 {
135         /* block for buffer acces and reset immediately. we can't do this
136          * in the thread, because we need to allocate an OpenGL buffer, and
137          * that only works in the main thread */
138         thread_scoped_lock display_lock(display_mutex);
139         thread_scoped_lock buffers_lock(buffers_mutex);
140
141         display_outdated = true;
142         reset_time = time_dt();
143
144         reset_(buffer_params, samples);
145
146         gpu_need_tonemap = false;
147         gpu_need_tonemap_cond.notify_all();
148
149         pause_cond.notify_all();
150 }
151
152 bool Session::draw_gpu(BufferParams& buffer_params)
153 {
154         /* block for buffer access */
155         thread_scoped_lock display_lock(display_mutex);
156
157         /* first check we already rendered something */
158         if(gpu_draw_ready) {
159                 /* then verify the buffers have the expected size, so we don't
160                  * draw previous results in a resized window */
161                 if(!buffer_params.modified(display->params)) {
162                         /* for CUDA we need to do tonemapping still, since we can
163                          * only access GL buffers from the main thread */
164                         if(gpu_need_tonemap) {
165                                 thread_scoped_lock buffers_lock(buffers_mutex);
166                                 tonemap();
167                                 gpu_need_tonemap = false;
168                                 gpu_need_tonemap_cond.notify_all();
169                         }
170
171                         display->draw(device);
172
173                         if(display_outdated && (time_dt() - reset_time) > params.text_timeout)
174                                 return false;
175
176                         return true;
177                 }
178         }
179
180         return false;
181 }
182
183 void Session::run_gpu()
184 {
185         bool tiles_written = false;
186
187         start_time = time_dt();
188         reset_time = time_dt();
189         paused_time = 0.0;
190         last_update_time = time_dt();
191
192         if(!params.background)
193                 progress.set_start_time(start_time + paused_time);
194
195         while(!progress.get_cancel()) {
196                 /* advance to next tile */
197                 bool no_tiles = !tile_manager.next();
198
199                 if(params.background) {
200                         /* if no work left and in background mode, we can stop immediately */
201                         if(no_tiles) {
202                                 progress.set_status("Finished");
203                                 break;
204                         }
205                 }
206                 else {
207                         /* if in interactive mode, and we are either paused or done for now,
208                          * wait for pause condition notify to wake up again */
209                         thread_scoped_lock pause_lock(pause_mutex);
210
211                         if(pause || no_tiles) {
212                                 update_status_time(pause, no_tiles);
213
214                                 while(1) {
215                                         double pause_start = time_dt();
216                                         pause_cond.wait(pause_lock);
217                                         paused_time += time_dt() - pause_start;
218
219                                         if(!params.background)
220                                                 progress.set_start_time(start_time + paused_time);
221
222                                         update_status_time(pause, no_tiles);
223                                         progress.set_update();
224
225                                         if(!pause)
226                                                 break;
227                                 }
228                         }
229
230                         if(progress.get_cancel())
231                                 break;
232                 }
233
234                 if(!no_tiles) {
235                         /* update scene */
236                         update_scene();
237
238                         if(device->error_message() != "")
239                                 progress.set_cancel(device->error_message());
240
241                         if(progress.get_cancel())
242                                 break;
243                 }
244
245                 if(!no_tiles) {
246                         /* buffers mutex is locked entirely while rendering each
247                          * sample, and released/reacquired on each iteration to allow
248                          * reset and draw in between */
249                         thread_scoped_lock buffers_lock(buffers_mutex);
250
251                         /* update status and timing */
252                         update_status_time();
253
254                         /* path trace */
255                         path_trace();
256
257                         device->task_wait();
258
259                         if(device->error_message() != "")
260                                 progress.set_cancel(device->error_message());
261
262                         /* update status and timing */
263                         update_status_time();
264
265                         gpu_need_tonemap = true;
266                         gpu_draw_ready = true;
267                         progress.set_update();
268
269                         /* wait for tonemap */
270                         if(!params.background) {
271                                 while(gpu_need_tonemap) {
272                                         if(progress.get_cancel())
273                                                 break;
274
275                                         gpu_need_tonemap_cond.wait(buffers_lock);
276                                 }
277                         }
278
279                         if(device->error_message() != "")
280                                 progress.set_cancel(device->error_message());
281
282                         tiles_written = update_progressive_refine(progress.get_cancel());
283
284                         if(progress.get_cancel())
285                                 break;
286                 }
287         }
288
289         if(!tiles_written)
290                 update_progressive_refine(true);
291 }
292
293 /* CPU Session */
294
295 void Session::reset_cpu(BufferParams& buffer_params, int samples)
296 {
297         thread_scoped_lock reset_lock(delayed_reset.mutex);
298
299         display_outdated = true;
300         reset_time = time_dt();
301
302         delayed_reset.params = buffer_params;
303         delayed_reset.samples = samples;
304         delayed_reset.do_reset = true;
305         device->task_cancel();
306
307         pause_cond.notify_all();
308 }
309
310 bool Session::draw_cpu(BufferParams& buffer_params)
311 {
312         thread_scoped_lock display_lock(display_mutex);
313
314         /* first check we already rendered something */
315         if(display->draw_ready()) {
316                 /* then verify the buffers have the expected size, so we don't
317                  * draw previous results in a resized window */
318                 if(!buffer_params.modified(display->params)) {
319                         display->draw(device);
320
321                         if(display_outdated && (time_dt() - reset_time) > params.text_timeout)
322                                 return false;
323
324                         return true;
325                 }
326         }
327
328         return false;
329 }
330
331 bool Session::acquire_tile(Device *tile_device, RenderTile& rtile)
332 {
333         if(progress.get_cancel()) {
334                 if(params.progressive_refine == false) {
335                         /* for progressive refine current sample should be finished for all tiles */
336                         return false;
337                 }
338         }
339
340         thread_scoped_lock tile_lock(tile_mutex);
341
342         /* get next tile from manager */
343         Tile tile;
344         int device_num = device->device_number(tile_device);
345
346         if(!tile_manager.next_tile(tile, device_num))
347                 return false;
348         
349         /* fill render tile */
350         rtile.x = tile_manager.state.buffer.full_x + tile.x;
351         rtile.y = tile_manager.state.buffer.full_y + tile.y;
352         rtile.w = tile.w;
353         rtile.h = tile.h;
354         rtile.start_sample = tile_manager.state.sample;
355         rtile.num_samples = tile_manager.state.num_samples;
356         rtile.resolution = tile_manager.state.resolution_divider;
357
358         tile_lock.unlock();
359
360         /* in case of a permanent buffer, return it, otherwise we will allocate
361          * a new temporary buffer */
362         if(!params.background) {
363                 tile_manager.state.buffer.get_offset_stride(rtile.offset, rtile.stride);
364
365                 rtile.buffer = buffers->buffer.device_pointer;
366                 rtile.rng_state = buffers->rng_state.device_pointer;
367                 rtile.rgba = display->rgba.device_pointer;
368                 rtile.buffers = buffers;
369
370                 device->map_tile(tile_device, rtile);
371
372                 return true;
373         }
374
375         /* fill buffer parameters */
376         BufferParams buffer_params = tile_manager.params;
377         buffer_params.full_x = rtile.x;
378         buffer_params.full_y = rtile.y;
379         buffer_params.width = rtile.w;
380         buffer_params.height = rtile.h;
381
382         buffer_params.get_offset_stride(rtile.offset, rtile.stride);
383
384         RenderBuffers *tilebuffers = new RenderBuffers(tile_device);
385
386         /* allocate buffers */
387         if(params.progressive_refine) {
388                 tile_lock.lock();
389
390                 if(tile_buffers.size() == 0)
391                         tile_buffers.resize(tile_manager.state.num_tiles, NULL);
392
393                 tilebuffers = tile_buffers[tile.index];
394                 if(tilebuffers == NULL) {
395                         tilebuffers = new RenderBuffers(tile_device);
396                         tile_buffers[tile.index] = tilebuffers;
397
398                         tilebuffers->reset(tile_device, buffer_params);
399                 }
400
401                 tile_lock.unlock();
402         }
403         else {
404                 tilebuffers = new RenderBuffers(tile_device);
405
406                 tilebuffers->reset(tile_device, buffer_params);
407         }
408
409         rtile.buffer = tilebuffers->buffer.device_pointer;
410         rtile.rng_state = tilebuffers->rng_state.device_pointer;
411         rtile.rgba = 0;
412         rtile.buffers = tilebuffers;
413
414         /* this will tag tile as IN PROGRESS in blender-side render pipeline,
415          * which is needed to highlight currently rendering tile before first
416          * sample was processed for it
417          */
418         update_tile_sample(rtile);
419
420         return true;
421 }
422
423 void Session::update_tile_sample(RenderTile& rtile)
424 {
425         thread_scoped_lock tile_lock(tile_mutex);
426
427         if(update_render_tile_cb) {
428                 if(params.progressive_refine == false) {
429                         /* todo: optimize this by making it thread safe and removing lock */
430
431                         update_render_tile_cb(rtile);
432                 }
433         }
434
435         update_status_time();
436 }
437
438 void Session::release_tile(RenderTile& rtile)
439 {
440         thread_scoped_lock tile_lock(tile_mutex);
441
442         if(write_render_tile_cb) {
443                 if(params.progressive_refine == false) {
444                         /* todo: optimize this by making it thread safe and removing lock */
445                         write_render_tile_cb(rtile);
446
447                         delete rtile.buffers;
448                 }
449         }
450
451         update_status_time();
452 }
453
454 void Session::run_cpu()
455 {
456         bool tiles_written = false;
457
458         last_update_time = time_dt();
459
460         {
461                 /* reset once to start */
462                 thread_scoped_lock reset_lock(delayed_reset.mutex);
463                 thread_scoped_lock buffers_lock(buffers_mutex);
464                 thread_scoped_lock display_lock(display_mutex);
465
466                 reset_(delayed_reset.params, delayed_reset.samples);
467                 delayed_reset.do_reset = false;
468         }
469
470         while(!progress.get_cancel()) {
471                 /* advance to next tile */
472                 bool no_tiles = !tile_manager.next();
473                 bool need_tonemap = false;
474
475                 if(params.background) {
476                         /* if no work left and in background mode, we can stop immediately */
477                         if(no_tiles) {
478                                 progress.set_status("Finished");
479                                 break;
480                         }
481                 }
482                 else {
483                         /* if in interactive mode, and we are either paused or done for now,
484                          * wait for pause condition notify to wake up again */
485                         thread_scoped_lock pause_lock(pause_mutex);
486
487                         if(pause || no_tiles) {
488                                 update_status_time(pause, no_tiles);
489
490                                 while(1) {
491                                         double pause_start = time_dt();
492                                         pause_cond.wait(pause_lock);
493                                         paused_time += time_dt() - pause_start;
494
495                                         if(!params.background)
496                                                 progress.set_start_time(start_time + paused_time);
497
498                                         update_status_time(pause, no_tiles);
499                                         progress.set_update();
500
501                                         if(!pause)
502                                                 break;
503                                 }
504                         }
505
506                         if(progress.get_cancel())
507                                 break;
508                 }
509
510                 if(!no_tiles) {
511                         /* buffers mutex is locked entirely while rendering each
512                          * sample, and released/reacquired on each iteration to allow
513                          * reset and draw in between */
514                         thread_scoped_lock buffers_lock(buffers_mutex);
515
516                         /* update scene */
517                         update_scene();
518
519                         if(device->error_message() != "")
520                                 progress.set_cancel(device->error_message());
521
522                         if(progress.get_cancel())
523                                 break;
524
525                         /* update status and timing */
526                         update_status_time();
527
528                         /* path trace */
529                         path_trace();
530
531                         /* update status and timing */
532                         update_status_time();
533
534                         if(!params.background)
535                                 need_tonemap = true;
536
537                         if(device->error_message() != "")
538                                 progress.set_cancel(device->error_message());
539                 }
540
541                 device->task_wait();
542
543                 {
544                         thread_scoped_lock reset_lock(delayed_reset.mutex);
545                         thread_scoped_lock buffers_lock(buffers_mutex);
546                         thread_scoped_lock display_lock(display_mutex);
547
548                         if(delayed_reset.do_reset) {
549                                 /* reset rendering if request from main thread */
550                                 delayed_reset.do_reset = false;
551                                 reset_(delayed_reset.params, delayed_reset.samples);
552                         }
553                         else if(need_tonemap) {
554                                 /* tonemap only if we do not reset, we don't we don't
555                                  * want to show the result of an incomplete sample*/
556                                 tonemap();
557                         }
558
559                         if(device->error_message() != "")
560                                 progress.set_cancel(device->error_message());
561
562                         tiles_written = update_progressive_refine(progress.get_cancel());
563                 }
564
565                 progress.set_update();
566         }
567
568         if(!tiles_written)
569                 update_progressive_refine(true);
570 }
571
572 void Session::run()
573 {
574         /* load kernels */
575         if(!kernels_loaded) {
576                 progress.set_status("Loading render kernels (may take a few minutes the first time)");
577
578                 if(!device->load_kernels(params.experimental)) {
579                         string message = device->error_message();
580                         if(message == "")
581                                 message = "Failed loading render kernel, see console for errors";
582
583                         progress.set_status("Error", message);
584                         progress.set_update();
585                         return;
586                 }
587
588                 kernels_loaded = true;
589         }
590
591         /* session thread loop */
592         progress.set_status("Waiting for render to start");
593
594         /* run */
595         if(!progress.get_cancel()) {
596                 /* reset number of rendered samples */
597                 progress.reset_sample();
598
599                 if(device_use_gl)
600                         run_gpu();
601                 else
602                         run_cpu();
603         }
604
605         /* progress update */
606         if(progress.get_cancel())
607                 progress.set_status("Cancel", progress.get_cancel_message());
608         else
609                 progress.set_update();
610 }
611
612 bool Session::draw(BufferParams& buffer_params)
613 {
614         if(device_use_gl)
615                 return draw_gpu(buffer_params);
616         else
617                 return draw_cpu(buffer_params);
618 }
619
620 void Session::reset_(BufferParams& buffer_params, int samples)
621 {
622         if(buffers) {
623                 if(buffer_params.modified(buffers->params)) {
624                         gpu_draw_ready = false;
625                         buffers->reset(device, buffer_params);
626                         display->reset(device, buffer_params);
627                 }
628         }
629
630         tile_manager.reset(buffer_params, samples);
631
632         start_time = time_dt();
633         preview_time = 0.0;
634         paused_time = 0.0;
635
636         if(!params.background)
637                 progress.set_start_time(start_time + paused_time);
638 }
639
640 void Session::reset(BufferParams& buffer_params, int samples)
641 {
642         if(device_use_gl)
643                 reset_gpu(buffer_params, samples);
644         else
645                 reset_cpu(buffer_params, samples);
646
647         if(params.progressive_refine) {
648                 thread_scoped_lock buffers_lock(buffers_mutex);
649
650                 foreach(RenderBuffers *buffers, tile_buffers)
651                         delete buffers;
652
653                 tile_buffers.clear();
654         }
655 }
656
657 void Session::set_samples(int samples)
658 {
659         if(samples != params.samples) {
660                 params.samples = samples;
661                 tile_manager.set_samples(samples);
662
663                 {
664                         thread_scoped_lock pause_lock(pause_mutex);
665                 }
666                 pause_cond.notify_all();
667         }
668 }
669
670 void Session::set_pause(bool pause_)
671 {
672         bool notify = false;
673
674         {
675                 thread_scoped_lock pause_lock(pause_mutex);
676
677                 if(pause != pause_) {
678                         pause = pause_;
679                         notify = true;
680                 }
681         }
682
683         if(notify)
684                 pause_cond.notify_all();
685 }
686
687 void Session::wait()
688 {
689         session_thread->join();
690         delete session_thread;
691
692         session_thread = NULL;
693 }
694
695 void Session::update_scene()
696 {
697         thread_scoped_lock scene_lock(scene->mutex);
698
699         /* update camera if dimensions changed for progressive render. the camera
700          * knows nothing about progressive or cropped rendering, it just gets the
701          * image dimensions passed in */
702         Camera *cam = scene->camera;
703         int width = tile_manager.state.buffer.full_width;
704         int height = tile_manager.state.buffer.full_height;
705
706         if(width != cam->width || height != cam->height) {
707                 cam->width = width;
708                 cam->height = height;
709                 cam->tag_update();
710         }
711
712         /* update scene */
713         if(scene->need_update()) {
714                 progress.set_status("Updating Scene");
715                 scene->device_update(device, progress);
716         }
717 }
718
719 void Session::update_status_time(bool show_pause, bool show_done)
720 {
721         int sample = tile_manager.state.sample;
722         int resolution = tile_manager.state.resolution_divider;
723         int num_tiles = tile_manager.state.num_tiles;
724         int tile = tile_manager.state.num_rendered_tiles;
725
726         /* update status */
727         string status, substatus;
728
729         if(!params.progressive) {
730                 bool is_gpu = params.device.type == DEVICE_CUDA || params.device.type == DEVICE_OPENCL;
731                 bool is_multidevice = params.device.multi_devices.size() > 1;
732                 bool is_cpu = params.device.type == DEVICE_CPU;
733
734                 substatus = string_printf("Path Tracing Tile %d/%d", tile, num_tiles);
735
736                 if((is_gpu && !is_multidevice) || (is_cpu && num_tiles == 1)) {
737                         /* when rendering on GPU multithreading happens within single tile, as in
738                          * tiles are handling sequentially and in this case we could display
739                          * currently rendering sample number
740                          * this helps a lot from feedback point of view.
741                          * also display the info on CPU, when using 1 tile only
742                          */
743
744                         int sample = progress.get_sample(), num_samples = tile_manager.state.num_samples;
745
746                         if(tile > 1) {
747                                 /* sample counter is global for all tiles, subtract samples
748                                  * from already finished tiles to get sample counter for
749                                  * current tile only
750                                  */
751                                 sample -= (tile - 1) * num_samples;
752                         }
753
754                         substatus += string_printf(", Sample %d/%d", sample, num_samples);
755                 }
756         }
757         else if(params.samples == INT_MAX)
758                 substatus = string_printf("Path Tracing Sample %d", sample+1);
759         else
760                 substatus = string_printf("Path Tracing Sample %d/%d", sample+1, params.samples);
761         
762         if(show_pause)
763                 status = "Paused";
764         else if(show_done)
765                 status = "Done";
766         else
767                 status = "Rendering";
768
769         progress.set_status(status, substatus);
770
771         /* update timing */
772         if(preview_time == 0.0 && resolution == 1)
773                 preview_time = time_dt();
774         
775         double tile_time = (tile == 0 || sample == 0)? 0.0: (time_dt() - preview_time - paused_time) / sample;
776
777         /* negative can happen when we pause a bit before rendering, can discard that */
778         if(preview_time < 0.0) preview_time = 0.0;
779
780         progress.set_tile(tile, tile_time);
781 }
782
783 void Session::update_progress_sample()
784 {
785         progress.increment_sample();
786 }
787
788 void Session::path_trace()
789 {
790         /* add path trace task */
791         DeviceTask task(DeviceTask::PATH_TRACE);
792         
793         task.acquire_tile = function_bind(&Session::acquire_tile, this, _1, _2);
794         task.release_tile = function_bind(&Session::release_tile, this, _1);
795         task.get_cancel = function_bind(&Progress::get_cancel, &this->progress);
796         task.update_tile_sample = function_bind(&Session::update_tile_sample, this, _1);
797         task.update_progress_sample = function_bind(&Session::update_progress_sample, this);
798         task.need_finish_queue = params.progressive_refine;
799
800         device->task_add(task);
801 }
802
803 void Session::tonemap()
804 {
805         /* add tonemap task */
806         DeviceTask task(DeviceTask::TONEMAP);
807
808         task.x = tile_manager.state.buffer.full_x;
809         task.y = tile_manager.state.buffer.full_y;
810         task.w = tile_manager.state.buffer.width;
811         task.h = tile_manager.state.buffer.height;
812         task.rgba = display->rgba.device_pointer;
813         task.buffer = buffers->buffer.device_pointer;
814         task.sample = tile_manager.state.sample;
815         task.resolution = tile_manager.state.resolution_divider;
816         tile_manager.state.buffer.get_offset_stride(task.offset, task.stride);
817
818         if(task.w > 0 && task.h > 0) {
819                 device->task_add(task);
820                 device->task_wait();
821
822                 /* set display to new size */
823                 display->draw_set(task.w, task.h);
824         }
825
826         display_outdated = false;
827 }
828
829 bool Session::update_progressive_refine(bool cancel)
830 {
831         int sample = tile_manager.state.sample + 1;
832         bool write = sample == params.samples || cancel;
833
834         double current_time = time_dt();
835
836         if (current_time - last_update_time < 1.0) {
837                 /* if last sample was processed, we need to write buffers anyway  */
838                 if (!write)
839                         return false;
840         }
841
842         if(params.progressive_refine) {
843                 foreach(RenderBuffers *buffers, tile_buffers) {
844                         RenderTile rtile;
845                         rtile.buffers = buffers;
846                         rtile.sample = sample;
847
848                         if(write)
849                                 write_render_tile_cb(rtile);
850                         else
851                                 update_render_tile_cb(rtile);
852                 }
853         }
854
855         last_update_time = current_time;
856
857         return write;
858 }
859
860 void Session::device_free()
861 {
862         scene->device_free();
863
864         foreach(RenderBuffers *buffers, tile_buffers)
865                 delete buffers;
866
867         tile_buffers.clear();
868
869         /* used from background render only, so no need to
870          * re-create render/display buffers here
871          */
872 }
873
874 CCL_NAMESPACE_END