Blender V5.0
session/session.cpp
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
2 *
3 * SPDX-License-Identifier: Apache-2.0 */
4
5#include <cstring>
6
7#include "device/cpu/device.h"
8#include "device/device.h"
10#include "scene/background.h"
11#include "scene/camera.h"
12#include "scene/integrator.h"
13#include "scene/light.h"
14#include "scene/mesh.h"
15#include "scene/object.h"
16#include "scene/scene.h"
17#include "scene/shader_graph.h"
18#include "session/buffers.h"
21#include "session/session.h"
22
23#include "util/log.h"
24#include "util/math.h"
25#include "util/task.h"
26#include "util/time.h"
27
29
30Session::Session(const SessionParams &params_, const SceneParams &scene_params)
32{
34
35 delayed_reset_.do_reset = false;
36
37 pause_ = false;
38 new_work_added_ = false;
39
41
42 if (device->have_error()) {
43 progress.set_error(device->error_message());
44 }
45
46 scene = make_unique<Scene>(scene_params, device.get());
47
48 if (params.device == params.denoise_device) {
49 /* Reuse render device. */
50 }
51 else {
52 denoise_device_ = Device::create(params.denoise_device, stats, profiler, params_.headless);
53
54 if (denoise_device_->have_error()) {
55 progress.set_error(denoise_device_->error_message());
56 }
57 }
58
59 /* Configure path tracer. */
60 path_trace_ = make_unique<PathTrace>(device.get(),
62 scene->film,
63 &scene->dscene,
66 path_trace_->set_progress(&progress);
67 path_trace_->progress_update_cb = [&]() { update_status_time(); };
68
69 tile_manager_.full_buffer_written_cb = [&](string_view filename) {
71 return;
72 }
73 full_buffer_written_cb(filename);
74 };
75
76 /* Create session thread. */
77 session_thread_ = make_unique<thread>([this] { thread_run(); });
78}
79
81{
82 /* Cancel any ongoing render operation. */
83 cancel();
84
85 /* Signal session thread to end. */
86 {
87 const thread_scoped_lock session_thread_lock(session_thread_mutex_);
89 }
90 session_thread_cond_.notify_all();
91
92 /* Destroy session thread. */
93 session_thread_->join();
94 session_thread_.reset();
95
96 /* Destroy path tracer, before the device. This is needed because destruction might need to
97 * access device for device memory free.
98 * TODO(sergey): Convert device to be unique_ptr, and rely on C++ to destruct objects in the
99 * pre-defined order. */
100 path_trace_.reset();
101
102 /* Destroy scene and device. */
103 scene.reset();
104 denoise_device_.reset();
105 device.reset();
106
107 /* Stop task scheduler. */
109}
110
112{
113 {
114 /* Signal session thread to start rendering. */
115 const thread_scoped_lock session_thread_lock(session_thread_mutex_);
117 /* Already rendering, nothing to do. */
118 return;
119 }
121 }
122
123 session_thread_cond_.notify_all();
124}
125
126void Session::cancel(bool quick)
127{
128 /* Cancel any long running device operations (e.g. shader compilations). */
129 device->cancel();
130
131 /* Check if session thread is rendering. */
132 const bool rendering = is_session_thread_rendering();
133
134 if (rendering) {
135 /* Cancel path trace operations. */
136 if (quick && path_trace_) {
137 path_trace_->cancel();
138 }
139
140 /* Cancel other operations. */
141 progress.set_cancel("Exiting");
142
143 /* Signal unpause in case the render was paused. */
144 {
145 const thread_scoped_lock pause_lock(pause_mutex_);
146 pause_ = false;
147 }
148 pause_cond_.notify_all();
149
150 /* Wait for render thread to be cancelled or finished. */
151 wait();
152 }
153}
154
156{
157 return path_trace_->ready_to_reset();
158}
159
161{
162 path_trace_->zero_display();
163
164 while (true) {
166
167 if (!render_work) {
169 double total_time;
170 double render_time;
171 progress.get_time(total_time, render_time);
172 LOG_INFO << "Rendering in main loop is done in " << render_time << " seconds.";
173 LOG_INFO << path_trace_->full_report();
174 }
175
176 if (params.background) {
177 /* if no work left and in background mode, we can stop immediately. */
178 progress.set_status("Finished");
179 break;
180 }
181 }
182
183 const bool did_cancel = progress.get_cancel();
184 if (did_cancel) {
185 render_scheduler_.render_work_reschedule_on_cancel(render_work);
186 if (!render_work) {
187 break;
188 }
189 }
190 else if (run_wait_for_work(render_work)) {
191 continue;
192 }
193
194 /* Stop rendering if error happened during scene update or other step of preparing scene
195 * for render. */
196 if (device->have_error()) {
197 progress.set_error(device->error_message());
198 break;
199 }
200
201 {
202 /* buffers mutex is locked entirely while rendering each
203 * sample, and released/reacquired on each iteration to allow
204 * reset and draw in between */
205 const thread_scoped_lock buffers_lock(buffers_mutex_);
206
207 /* update status and timing */
209
210 /* render */
211 path_trace_->render(render_work);
212
213 /* update status and timing */
215
216 /* Stop rendering if error happened during path tracing. */
217 if (device->have_error()) {
218 progress.set_error(device->error_message());
219 break;
220 }
221 }
222
223 progress.set_update();
224
225 if (did_cancel) {
226 break;
227 }
228 }
229}
230
232{
233 while (true) {
234 {
235 thread_scoped_lock session_thread_lock(session_thread_mutex_);
236
238 /* Continue waiting for any signal from the main thread. */
239 session_thread_cond_.wait(session_thread_lock);
240 continue;
241 }
243 /* End thread immediately. */
244 break;
245 }
246 }
247
248 /* Execute a render. */
250
251 /* Go back from rendering to waiting. */
252 {
253 const thread_scoped_lock session_thread_lock(session_thread_mutex_);
256 }
257 }
258 session_thread_cond_.notify_all();
259 }
260
261 /* Flush any remaining operations and destroy display driver here. This ensure
262 * graphics API resources are created and destroyed all in the session thread,
263 * which can avoid problems contexts and multiple threads. */
264 path_trace_->flush_display();
265 path_trace_->set_display_driver(nullptr);
266}
267
269{
270 if (params.use_profiling && (params.device.type == DEVICE_CPU)) {
271 profiler.start();
272 }
273
274 /* session thread loop */
275 progress.set_status("Waiting for render to start");
276
277 /* run */
278 if (!progress.get_cancel()) {
279 /* reset number of rendered samples */
280 progress.reset_sample();
281
283 }
284
285 profiler.stop();
286
287 /* progress update */
288 if (progress.get_cancel()) {
289 progress.set_status(progress.get_cancel_message());
290 }
291 else {
292 progress.set_update();
293 }
294}
295
301
303{
304 RenderWork render_work;
305
306 thread_scoped_lock scene_lock(scene->mutex);
307
308 /* Perform delayed reset if requested. */
309 const bool reset_buffers = delayed_reset_buffer_params();
310
311 /* Update scene */
312 const bool reset_scene = update_scene(delayed_reset_.do_reset);
313
314 /* Update buffers for new parameters. After scene update which influences the passes used. */
315 bool have_tiles = true;
316 bool switched_to_new_tile = false;
317
318 if (reset_buffers) {
320
321 /* After reset make sure the tile manager is at the first big tile. */
322 have_tiles = tile_manager_.next();
323 switched_to_new_tile = true;
324 }
325
326 /* Update denoiser settings. */
327 {
328 const DenoiseParams denoise_params = scene->integrator->get_denoise_params();
329 path_trace_->set_denoiser_params(denoise_params);
330 }
331
332 /* Update adaptive sampling. */
333 {
334 const AdaptiveSampling adaptive_sampling = scene->integrator->get_adaptive_sampling();
335 path_trace_->set_adaptive_sampling(adaptive_sampling);
336 }
337
338 /* Update path guiding. */
339 {
340 const GuidingParams guiding_params = scene->integrator->get_guiding_params(device.get());
341 const bool guiding_reset = (guiding_params.use) ? reset_scene : false;
342 path_trace_->set_guiding_params(guiding_params, guiding_reset);
343 }
344
345 render_scheduler_.set_sample_params(params.samples,
346 params.use_sample_subset,
347 params.sample_subset_offset,
348 params.sample_subset_length);
349 render_scheduler_.set_time_limit(params.time_limit);
350
351 while (have_tiles) {
352 render_work = render_scheduler_.get_render_work();
353 if (render_work) {
354 break;
355 }
356
357 progress.add_finished_tile(false);
358
359 have_tiles = tile_manager_.next();
360 if (have_tiles) {
361 render_scheduler_.reset_for_next_tile();
362 switched_to_new_tile = true;
363 }
364 }
365
366 if (render_work) {
367 const scoped_timer update_timer;
368
369 if (switched_to_new_tile) {
370 BufferParams tile_params = buffer_params_;
371
372 const Tile &tile = tile_manager_.get_current_tile();
373
374 tile_params.width = tile.width;
375 tile_params.height = tile.height;
376
377 tile_params.window_x = tile.window_x;
378 tile_params.window_y = tile.window_y;
379 tile_params.window_width = tile.window_width;
380 tile_params.window_height = tile.window_height;
381
382 tile_params.full_x = tile.x + buffer_params_.full_x;
383 tile_params.full_y = tile.y + buffer_params_.full_y;
384 tile_params.full_width = buffer_params_.full_width;
385 tile_params.full_height = buffer_params_.full_height;
386
387 tile_params.update_offset_stride();
388
389 path_trace_->reset(buffer_params_, tile_params, reset_buffers);
390 }
391
392 /* Update camera if dimensions changed for progressive render. the camera
393 * knows nothing about progressive or cropped rendering, it just gets the
394 * image dimensions passed in. */
395 const int resolution = render_work.resolution_divider;
396 const int width = max(1, buffer_params_.full_width / resolution);
397 const int height = max(1, buffer_params_.full_height / resolution);
398
399 scene->update_camera_resolution(progress, width, height);
400
401 /* Unlock scene mutex before loading denoiser kernels, since that may attempt to activate
402 * graphics interop, which can deadlock when the scene mutex is still being held. */
403 scene_lock.unlock();
404
405 path_trace_->load_kernels();
406 path_trace_->alloc_work_memory();
407
408 /* Wait for device to be ready (e.g. finish any background compilations). */
409 string device_status;
410 while (!device->is_ready(device_status)) {
411 progress.set_status(device_status);
412 if (progress.get_cancel()) {
413 break;
414 }
415 std::this_thread::sleep_for(std::chrono::milliseconds(200));
416 }
417
418 progress.add_skip_time(update_timer, params.background);
419 }
420
421 return render_work;
422}
423
425{
426 /* In an offline rendering there is no pause, and no tiles will mean the job is fully done. */
427 if (params.background) {
428 return false;
429 }
430
432
433 if (!pause_ && render_work) {
434 /* Rendering is not paused and there is work to be done. No need to wait for anything. */
435 return false;
436 }
437
438 const bool no_work = !render_work;
439 update_status_time(pause_, no_work);
440
441 /* Only leave the loop when rendering is not paused. But even if the current render is
442 * un-paused but there is nothing to render keep waiting until new work is added. */
443 while (!progress.get_cancel()) {
444 const scoped_timer pause_timer;
445
446 if (!pause_ && (render_work || new_work_added_ || delayed_reset_.do_reset)) {
447 break;
448 }
449
450 /* Wait for either pause state changed, or extra samples added to render. */
451 pause_cond_.wait(pause_lock);
452
453 if (pause_) {
454 progress.add_skip_time(pause_timer, params.background);
455 }
456
457 update_status_time(pause_, no_work);
458 progress.set_update();
459 }
460
461 new_work_added_ = false;
462
463 return no_work;
464}
465
467{
468 path_trace_->draw();
469}
470
472{
473 const int image_width = buffer_params_.width;
474 const int image_height = buffer_params_.height;
475
476 if (!params.use_auto_tile) {
478 }
479
480 const int64_t image_area = static_cast<int64_t>(image_width) * image_height;
481
482 /* TODO(sergey): Take available memory into account, and if there is enough memory do not
483 * tile and prefer optimal performance. */
484
485 const int tile_size = tile_manager_.compute_render_tile_size(params.tile_size);
486 const int64_t actual_tile_area = static_cast<int64_t>(tile_size) * tile_size;
487
488 if (actual_tile_area >= image_area && image_width <= TileManager::MAX_TILE_SIZE &&
490 {
492 }
493
494 return make_int2(tile_size, tile_size);
495}
496
498{
499 /* Reset buffer parameters, delayed from when we got the reset call so we can complete
500 * rendering the sample. Otherwise e.g. viewport navigation might reset without ever
501 * finishing anything. */
502 const thread_scoped_lock reset_lock(delayed_reset_.mutex);
503 if (!delayed_reset_.do_reset) {
504 return false;
505 }
506
507 const thread_scoped_lock buffers_lock(buffers_mutex_);
508 delayed_reset_.do_reset = false;
509
510 params = delayed_reset_.session_params;
511 buffer_params_ = delayed_reset_.buffer_params;
512
513 /* Store parameters used for buffers access outside of scene graph. */
515 buffer_params_.exposure = scene->film->get_exposure();
516 buffer_params_.use_approximate_shadow_catcher =
517 scene->film->get_use_approximate_shadow_catcher();
518 buffer_params_.use_transparent_background = scene->background->get_transparent();
519
520 /* Tile and work scheduling. */
522
523 return true;
524}
525
527{
528 render_scheduler_.set_sample_params(params.samples,
529 params.use_sample_subset,
530 params.sample_subset_offset,
531 params.sample_subset_length);
533
534 /* Update for new state of scene and passes. */
535 buffer_params_.update_passes(scene->passes);
536 tile_manager_.update(buffer_params_, scene.get());
537
538 /* Update temp directory on reset.
539 * This potentially allows to finish the existing rendering with a previously configure
540 * temporary
541 * directory in the host software and switch to a new temp directory when new render starts. */
542 tile_manager_.set_temp_dir(params.temp_dir);
543
544 /* Progress. */
545 progress.reset_sample();
546 progress.set_total_pixel_samples(static_cast<uint64_t>(buffer_params_.width) *
547 buffer_params_.height * buffer_params_.samples);
548
549 if (!params.background) {
550 progress.set_start_time();
551 }
552 const double time_limit = params.time_limit * ((double)tile_manager_.get_num_tiles());
553 progress.set_render_start_time();
554 progress.set_time_limit(time_limit);
555}
556
557void Session::reset(const SessionParams &session_params, const BufferParams &buffer_params)
558{
559 {
560 const thread_scoped_lock reset_lock(delayed_reset_.mutex);
561 const thread_scoped_lock pause_lock(pause_mutex_);
562
563 delayed_reset_.do_reset = true;
564 delayed_reset_.session_params = session_params;
565 delayed_reset_.buffer_params = buffer_params;
566
567 scene->scene_updated_while_loading_kernels = true;
568
569 path_trace_->cancel();
570 }
571
572 pause_cond_.notify_all();
573}
574
575void Session::set_samples(const int samples)
576{
577 if (samples == params.samples) {
578 return;
579 }
580
581 params.samples = samples;
582
583 {
584 const thread_scoped_lock pause_lock(pause_mutex_);
585 new_work_added_ = true;
586 }
587
588 pause_cond_.notify_all();
589}
590
591void Session::set_time_limit(const double time_limit)
592{
593 if (time_limit == params.time_limit) {
594 return;
595 }
596
597 params.time_limit = time_limit;
598
599 {
600 const thread_scoped_lock pause_lock(pause_mutex_);
601 new_work_added_ = true;
602 }
603
604 pause_cond_.notify_all();
605}
606
607void Session::set_pause(bool pause)
608{
609 bool notify = false;
610
611 {
612 const thread_scoped_lock pause_lock(pause_mutex_);
613
614 if (pause != pause_) {
615 pause_ = pause;
616 notify = true;
617 }
618 }
619
621 if (notify) {
622 pause_cond_.notify_all();
623 }
624 }
625 else if (pause_) {
627 }
628}
629
631{
632 path_trace_->set_output_driver(std::move(driver));
633}
634
636{
637 path_trace_->set_display_driver(std::move(driver));
638}
639
641{
642 const double completed = progress.get_progress();
643 if (completed == 0.0) {
644 return 0.0;
645 }
646
647 double total_time;
648 double render_time;
649 progress.get_time(total_time, render_time);
650 double remaining = (1.0 - (double)completed) * (render_time / (double)completed);
651
652 const double time_limit = render_scheduler_.get_time_limit() *
653 ((double)tile_manager_.get_num_tiles());
654 if (time_limit != 0.0) {
655 remaining = min(remaining, max(time_limit - render_time, 0.0));
656 }
657
658 return remaining;
659}
660
662{
663 /* Wait until session thread either is waiting or ending. */
664 while (true) {
665 thread_scoped_lock session_thread_lock(session_thread_mutex_);
667 break;
668 }
669 session_thread_cond_.wait(session_thread_lock);
670 }
671}
672
673bool Session::update_scene(const bool reset_samples)
674{
675 /* Update number of samples in the integrator.
676 * Ideally this would need to happen once in `Session::set_samples()`, but the issue there is
677 * the initial configuration when Session is created where the `set_samples()` is not used.
678 *
679 * NOTE: Unless reset was requested only allow increasing number of samples. */
680 if (reset_samples || scene->integrator->get_aa_samples() < params.samples) {
681 scene->integrator->set_aa_samples(params.samples);
682 }
683
684 scene->integrator->set_use_sample_subset(params.use_sample_subset);
685 scene->integrator->set_sample_subset_offset(params.sample_subset_offset);
686 scene->integrator->set_sample_subset_length(params.sample_subset_length);
687
688 /* When multiple tiles are used SAMPLE_COUNT pass is used to keep track of possible partial
689 * tile results. */
690 scene->film->set_use_sample_count(tile_manager_.has_multiple_tiles());
691
692 const bool reset = scene->need_reset(false);
693
694 if (scene->update(progress)) {
695 profiler.reset(scene->shaders.size(), scene->objects.size());
696 }
697
698 return reset;
699}
700
701static string status_append(const string &status, const string &suffix)
702{
703 string prefix = status;
704 if (!prefix.empty()) {
705 prefix += ", ";
706 }
707 return prefix + suffix;
708}
709
710void Session::update_status_time(bool show_pause, bool show_done)
711{
712 string status;
713 string substatus;
714
715 const int current_tile = progress.get_rendered_tiles();
716 const int num_tiles = tile_manager_.get_num_tiles();
717
718 const int current_sample = progress.get_current_sample();
719 const int num_samples = render_scheduler_.get_num_samples();
720
721 /* TIle. */
722 if (tile_manager_.has_multiple_tiles()) {
723 substatus = status_append(substatus,
724 string_printf("Rendered %d/%d Tiles", current_tile, num_tiles));
725 }
726
727 /* Sample. */
728 if (!params.background && num_samples == Integrator::MAX_SAMPLES) {
729 substatus = status_append(substatus, string_printf("Sample %d", current_sample));
730 }
731 else {
732 substatus = status_append(substatus,
733 string_printf("Sample %d/%d", current_sample, num_samples));
734 }
735
736 /* Append any device-specific status (such as background kernel optimization) */
737 string device_status;
738 if (device->is_ready(device_status) && !device_status.empty()) {
739 substatus += string_printf(" (%s)", device_status.c_str());
740 }
741
742 /* TODO(sergey): Denoising status from the path trace. */
743
744 if (show_pause) {
745 status = "Rendering Paused";
746 }
747 else if (show_done) {
748 status = "Rendering Done";
749 progress.set_end_time(); /* Save end time so that further calls to get_time are accurate. */
750 }
751 else {
752 status = substatus;
753 substatus.clear();
754 }
755
756 progress.set_status(status, substatus);
757}
758
760{
761 scene->device_free();
762 path_trace_->device_free();
763}
764
766{
767 scene->collect_statistics(render_stats);
768 if (params.use_profiling && (params.device.type == DEVICE_CPU)) {
769 render_stats->collect_profiling(scene.get(), profiler);
770 }
771}
772
773/* --------------------------------------------------------------------
774 * Full-frame on-disk storage.
775 */
776
778{
779 path_trace_->process_full_buffer_from_disk(filename);
780}
781
static constexpr int image_width
static constexpr int image_height
long long int int64_t
unsigned long long int uint64_t
int full_width
Definition buffers.h:85
int window_y
Definition buffers.h:78
void update_offset_stride()
Definition buffers.cpp:218
int full_height
Definition buffers.h:86
int window_height
Definition buffers.h:80
int window_width
Definition buffers.h:79
NODE_DECLARE int width
Definition buffers.h:70
int window_x
Definition buffers.h:77
static unique_ptr< Device > create(const DeviceInfo &info, Stats &stats, Profiler &profiler, bool headless)
static const int MAX_SAMPLES
Definition integrator.h:80
void thread_run()
unique_ptr< Scene > scene
void collect_statistics(RenderStats *stats)
RenderScheduler render_scheduler_
unique_ptr< thread > session_thread_
enum Session::@160214110334236026320245177267173251377254361251 session_thread_state_
void update_status_time(bool show_pause=false, bool show_done=false)
void set_pause(bool pause)
thread_mutex pause_mutex_
Device * denoise_device()
int2 get_effective_tile_size() const
void process_full_buffer_from_disk(string_view filename)
void set_display_driver(unique_ptr< DisplayDriver > driver)
bool ready_to_reset()
std::function< void(string_view)> full_buffer_written_cb
void cancel(bool quick=false)
@ SESSION_THREAD_RENDER
thread_condition_variable pause_cond_
bool update_scene(const bool reset_samples)
Progress progress
Profiler profiler
bool run_wait_for_work(const RenderWork &render_work)
double get_estimated_remaining_time() const
SessionParams params
thread_condition_variable session_thread_cond_
Session(const SessionParams &params, const SceneParams &scene_params)
void update_buffers_for_params()
void run_main_render_loop()
void thread_render()
thread_mutex session_thread_mutex_
BufferParams buffer_params_
unique_ptr< Device > denoise_device_
RenderWork run_update_for_next_iteration()
void set_time_limit(const double time_limit)
void reset(const SessionParams &session_params, const BufferParams &buffer_params)
bool is_session_thread_rendering()
void set_samples(const int samples)
void device_free()
unique_ptr< Device > device
thread_mutex buffers_mutex_
void set_output_driver(unique_ptr< OutputDriver > driver)
struct Session::DelayedReset delayed_reset_
bool delayed_reset_buffer_params()
unique_ptr< PathTrace > path_trace_
TileManager tile_manager_
bool new_work_added_
static void exit()
Definition task.cpp:81
static void init(const int num_threads=0)
Definition task.cpp:60
static const int MAX_TILE_SIZE
#define CCL_NAMESPACE_END
@ DEVICE_CPU
ccl_device_forceinline int2 make_int2(const int x, const int y)
ccl_gpu_kernel_postfix ccl_global KernelWorkTile const int num_tiles
const ccl_global KernelWorkTile * tile
#define LOG_IS_ON(level)
Definition log.h:113
@ LOG_LEVEL_INFO
Definition log.h:25
#define LOG_INFO
Definition log.h:106
const int status
static string status_append(const string &status, const string &suffix)
#define min(a, b)
Definition sort.cc:36
CCL_NAMESPACE_BEGIN string string_printf(const char *format,...)
Definition string.cpp:23
void collect_profiling(Scene *scene, Profiler &prof)
Definition stats.cpp:235
max
Definition text_draw.cc:251
std::unique_lock< std::mutex > thread_scoped_lock
Definition thread.h:28
double total_time