Blender V5.0
path_trace.cpp
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
2 *
3 * SPDX-License-Identifier: Apache-2.0 */
4
6
7#include "device/cpu/device.h"
8#include "device/device.h"
9
14
15#include "scene/pass.h"
16#include "scene/scene.h"
17
19#include "session/tile.h"
20
21#include "util/log.h"
22#include "util/progress.h"
23#include "util/tbb.h"
24#include "util/time.h"
25
27
29 Device *denoise_device,
30 Film *film,
31 DeviceScene *device_scene,
32 RenderScheduler &render_scheduler,
33 TileManager &tile_manager)
34 : device_(device),
35 denoise_device_(denoise_device),
36 film_(film),
37 device_scene_(device_scene),
38 render_scheduler_(render_scheduler),
39 tile_manager_(tile_manager)
40{
41 DCHECK_NE(device_, nullptr);
42
43 {
44 vector<DeviceInfo> cpu_devices;
45 device_cpu_info(cpu_devices);
46
48 cpu_devices[0], device->stats, device->profiler, device_->headless);
49 }
50
51 /* Create path tracing work in advance, so that it can be reused by incremental sampling as much
52 * as possible. */
53 device_->foreach_device([&](Device *path_trace_device) {
55 path_trace_device, film, device_scene, &render_cancel_.is_requested);
56 if (work) {
57 path_trace_works_.emplace_back(std::move(work));
58 }
59 });
60
63
64 render_scheduler.set_need_schedule_rebalance(path_trace_works_.size() > 1);
65}
66
71
73{
74 if (denoiser_) {
75 /* Activate graphics interop while denoiser device is created, so that it can choose a device
76 * that supports interop for faster display updates. */
77 if (display_ && path_trace_works_.size() > 1) {
78 display_->graphics_interop_activate();
79 }
80
81 denoiser_->load_kernels(progress_);
82
83 if (display_ && path_trace_works_.size() > 1) {
84 display_->graphics_interop_deactivate();
85 }
86 }
87}
88
90{
91 for (auto &&path_trace_work : path_trace_works_) {
92 path_trace_work->alloc_work_memory();
93 }
94}
95
97{
98 /* The logic here is optimized for the best feedback in the viewport, which implies having a GPU
99 * display. Of there is no such display, the logic here will break. */
101
102 /* The logic here tries to provide behavior which feels the most interactive feel to artists.
103 * General idea is to be able to reset as quickly as possible, while still providing interactive
104 * feel.
105 *
106 * If the render result was ever drawn after previous reset, consider that reset is now possible.
107 * This way camera navigation gives the quickest feedback of rendered pixels, regardless of
108 * whether CPU or GPU drawing pipeline is used.
109 *
110 * Consider reset happening after redraw "slow" enough to not clog anything. This is a bit
111 * arbitrary, but seems to work very well with viewport navigation in Blender. */
112
114 return true;
115 }
116
117 return false;
118}
119
120void PathTrace::reset(const BufferParams &full_params,
121 const BufferParams &big_tile_params,
122 const bool reset_rendering)
123{
124 if (big_tile_params_.modified(big_tile_params)) {
125 big_tile_params_ = big_tile_params;
126 render_state_.need_reset_params = true;
127 }
128
129 full_params_ = full_params;
130
131 /* NOTE: GPU display checks for buffer modification and avoids unnecessary re-allocation.
132 * It is requires to inform about reset whenever it happens, so that the redraw state tracking is
133 * properly updated. */
134 if (display_) {
135 display_->reset(big_tile_params, reset_rendering);
136 }
137
138 render_state_.has_denoised_result = false;
139 render_state_.tile_written = false;
140
141 did_draw_after_reset_ = false;
142}
143
145{
146 /* Free render buffers used by the path trace work to reduce memory peak. */
147 BufferParams empty_params;
148 empty_params.pass_stride = 0;
149 empty_params.update_offset_stride();
150 for (auto &&path_trace_work : path_trace_works_) {
151 path_trace_work->get_render_buffers()->reset(empty_params);
152 }
153 render_state_.need_reset_params = true;
154}
155
157{
158 progress_ = progress;
159}
160
161void PathTrace::render(const RenderWork &render_work)
162{
163 /* Indicate that rendering has started and that it can be requested to cancel. */
164 {
166 if (render_cancel_.is_requested) {
167 return;
168 }
169 render_cancel_.is_rendering = true;
170 }
171
172 render_pipeline(render_work);
173
174 /* Indicate that rendering has finished, making it so thread which requested `cancel()` can carry
175 * on. */
176 {
178 render_cancel_.is_rendering = false;
179 render_cancel_.condition.notify_one();
180 }
181}
182
184{
185 /* NOTE: Only check for "instant" cancel here. The user-requested cancel via progress is
186 * checked in Session and the work in the event of cancel is to be finished here. */
187
188 render_scheduler_.set_need_schedule_cryptomatte(device_scene_->data.film.cryptomatte_passes !=
189 0);
190
192
193 render_scheduler_.report_work_begin(render_work);
194
195 init_render_buffers(render_work);
196
197 rebalance(render_work);
198
199 /* Reset sample limit. */
200 render_scheduler_.set_limit_samples_per_update(0);
201
202 /* Prepare all per-thread guiding structures before we start with the next rendering
203 * iteration/progression. */
204 const bool use_guiding = device_scene_->data.integrator.use_guiding;
205 if (use_guiding) {
207 }
208
209 const bool has_volume = device_scene_->data.integrator.use_volumes;
210 if (has_volume) {
211 const uint num_rendered_samples = render_scheduler_.get_num_rendered_samples();
212 const uint limit = next_power_of_two(num_rendered_samples) - num_rendered_samples;
213 render_scheduler_.set_limit_samples_per_update(limit);
214 }
215
216 path_trace(render_work);
217 if (render_cancel_.is_requested) {
218 return;
219 }
220
221 /* Update the guiding field using the training data/samples collected during the rendering
222 * iteration/progression. */
223 const bool train_guiding = device_scene_->data.integrator.train_guiding;
224 if (use_guiding && train_guiding) {
226 }
227
228 adaptive_sample(render_work);
229 if (render_cancel_.is_requested) {
230 return;
231 }
232
233 cryptomatte_postprocess(render_work);
234 if (render_cancel_.is_requested) {
235 return;
236 }
237
238 denoise(render_work);
239 if (render_cancel_.is_requested) {
240 return;
241 }
242
243 denoise_volume_guiding_buffers(render_work, has_volume);
244 if (render_cancel_.is_requested) {
245 return;
246 }
247
248 write_tile_buffer(render_work);
249 update_display(render_work);
250
251 progress_update_if_needed(render_work);
252
253 finalize_full_buffer_on_disk(render_work);
254}
255
257{
258 for (auto &&path_trace_work : path_trace_works_) {
259 path_trace_work->init_execution();
260 }
261}
262
263/* TODO(sergey): Look into `std::function` rather than using a template. Should not be a
264 * measurable performance impact at runtime, but will make compilation faster and binary somewhat
265 * smaller. */
266template<typename Callback>
268 const vector<WorkBalanceInfo> &work_balance_infos,
269 const BufferParams &buffer_params,
270 const int overscan,
271 const Callback &callback)
272{
273 const int num_works = path_trace_works.size();
274 const int window_height = buffer_params.window_height;
275
276 int current_y = 0;
277 for (int i = 0; i < num_works; ++i) {
278 const double weight = work_balance_infos[i].weight;
279 const int slice_window_full_y = buffer_params.full_y + buffer_params.window_y + current_y;
280 const int slice_window_height = max(lround(window_height * weight), 1);
281
282 /* Disallow negative values to deal with situations when there are more compute devices than
283 * scan-lines. */
284 const int remaining_window_height = max(0, window_height - current_y);
285
286 BufferParams slice_params = buffer_params;
287
288 slice_params.full_y = max(slice_window_full_y - overscan, buffer_params.full_y);
289 slice_params.window_y = slice_window_full_y - slice_params.full_y;
290
291 if (i < num_works - 1) {
292 slice_params.window_height = min(slice_window_height, remaining_window_height);
293 }
294 else {
295 slice_params.window_height = remaining_window_height;
296 }
297
298 slice_params.height = slice_params.window_y + slice_params.window_height + overscan;
299 slice_params.height = min(slice_params.height,
300 buffer_params.height + buffer_params.full_y - slice_params.full_y);
301
302 slice_params.update_offset_stride();
303
304 callback(path_trace_works[i].get(), slice_params);
305
306 current_y += slice_params.window_height;
307 }
308}
309
311{
312 const int overscan = tile_manager_.get_tile_overscan();
316 overscan,
317 [](PathTraceWork *path_trace_work, const BufferParams &params) {
318 RenderBuffers *buffers = path_trace_work->get_render_buffers();
319 buffers->reset(params);
320 });
321}
322
323static BufferParams scale_buffer_params(const BufferParams &params, const int resolution_divider)
324{
325 BufferParams scaled_params = params;
326
327 scaled_params.width = max(1, params.width / resolution_divider);
328 scaled_params.height = max(1, params.height / resolution_divider);
329
330 scaled_params.window_x = params.window_x / resolution_divider;
331 scaled_params.window_y = params.window_y / resolution_divider;
332 scaled_params.window_width = max(1, params.window_width / resolution_divider);
333 scaled_params.window_height = max(1, params.window_height / resolution_divider);
334
335 scaled_params.full_x = params.full_x / resolution_divider;
336 scaled_params.full_y = params.full_y / resolution_divider;
337 scaled_params.full_width = max(1, params.full_width / resolution_divider);
338 scaled_params.full_height = max(1, params.full_height / resolution_divider);
339
340 scaled_params.update_offset_stride();
341
342 return scaled_params;
343}
344
346{
347 const int resolution_divider = render_work.resolution_divider;
348
350 const BufferParams scaled_big_tile_params = scale_buffer_params(big_tile_params_,
352
353 const int overscan = tile_manager_.get_tile_overscan();
354
357 scaled_big_tile_params,
358 overscan,
359 [&](PathTraceWork *path_trace_work, const BufferParams params) {
360 path_trace_work->set_effective_buffer_params(
361 scaled_full_params, scaled_big_tile_params, params);
362 });
363
364 render_state_.effective_big_tile_params = scaled_big_tile_params;
365}
366
368{
369 if (render_state_.need_reset_params) {
371 }
372
373 if (render_state_.need_reset_params ||
374 render_state_.resolution_divider != render_work.resolution_divider)
375 {
377 }
378
379 render_state_.resolution_divider = render_work.resolution_divider;
380 render_state_.need_reset_params = false;
381}
382
384{
386
387 /* Handle initialization scheduled by the render scheduler. */
388 if (render_work.init_render_buffers) {
389 parallel_for_each(path_trace_works_, [&](unique_ptr<PathTraceWork> &path_trace_work) {
390 path_trace_work->zero_render_buffers();
391 });
392
394 }
395}
396
398{
399 if (!render_work.path_trace.num_samples) {
400 return;
401 }
402
403 LOG_DEBUG << "Will path trace " << render_work.path_trace.num_samples
404 << " samples at the resolution divider " << render_work.resolution_divider;
405
406 const double start_time = time_dt();
407
408 const int num_works = path_trace_works_.size();
409
411
412 parallel_for(0, num_works, [&](int i) {
413 const double work_start_time = time_dt();
414 const int num_samples = render_work.path_trace.num_samples;
415
416 PathTraceWork *path_trace_work = path_trace_works_[i].get();
417 if (path_trace_work->get_device()->have_error()) {
418 return;
419 }
420
422 path_trace_work->render_samples(statistics,
423 render_work.path_trace.start_sample,
424 num_samples,
425 render_work.path_trace.sample_offset);
426
427 DCHECK(isfinite(statistics.occupancy));
428
429 const double work_time = time_dt() - work_start_time;
430 work_balance_infos_[i].time_spent += work_time;
431 work_balance_infos_[i].occupancy = statistics.occupancy;
432
433 LOG_INFO << "Rendered " << num_samples << " samples in " << work_time << " seconds ("
434 << work_time / num_samples
435 << " seconds per sample), occupancy: " << statistics.occupancy;
436 });
437
438 float occupancy_accum = 0.0f;
439 for (const WorkBalanceInfo &balance_info : work_balance_infos_) {
440 occupancy_accum += balance_info.occupancy;
441 }
442 const float occupancy = occupancy_accum / num_works;
443 render_scheduler_.report_path_trace_occupancy(render_work, occupancy);
444
445 render_scheduler_.report_path_trace_time(
446 render_work, time_dt() - start_time, is_cancel_requested());
447}
448
450{
451 if (!render_work.adaptive_sampling.filter) {
452 return;
453 }
454
455 bool did_reschedule_on_idle = false;
456
457 while (true) {
458 LOG_DEBUG << "Will filter adaptive stopping buffer, threshold "
459 << render_work.adaptive_sampling.threshold;
460 if (render_work.adaptive_sampling.reset) {
461 LOG_DEBUG << "Will re-calculate convergency flag for currently converged pixels.";
462 }
463
464 const double start_time = time_dt();
465
466 uint num_active_pixels = 0;
467 parallel_for_each(path_trace_works_, [&](unique_ptr<PathTraceWork> &path_trace_work) {
468 const uint num_active_pixels_in_work =
469 path_trace_work->adaptive_sampling_converge_filter_count_active(
470 render_work.adaptive_sampling.threshold, render_work.adaptive_sampling.reset);
471 if (num_active_pixels_in_work) {
472 atomic_add_and_fetch_u(&num_active_pixels, num_active_pixels_in_work);
473 }
474 });
475
476 render_scheduler_.report_adaptive_filter_time(
477 render_work, time_dt() - start_time, is_cancel_requested());
478
479 if (num_active_pixels == 0) {
480 LOG_DEBUG << "All pixels converged.";
481 if (!render_scheduler_.render_work_reschedule_on_converge(render_work)) {
482 break;
483 }
484 LOG_DEBUG << "Continuing with lower threshold.";
485 }
486 else if (did_reschedule_on_idle) {
487 break;
488 }
489 else if (num_active_pixels < 128 * 128) {
490 /* NOTE: The hardcoded value of 128^2 is more of an empirical value to keep GPU busy so that
491 * there is no performance loss from the progressive noise floor feature.
492 *
493 * A better heuristic is possible here: for example, use maximum of 128^2 and percentage of
494 * the final resolution. */
495 if (!render_scheduler_.render_work_reschedule_on_idle(render_work)) {
496 LOG_DEBUG << "Rescheduling is not possible: final threshold is reached.";
497 break;
498 }
499 LOG_DEBUG << "Rescheduling lower threshold.";
500 did_reschedule_on_idle = true;
501 }
502 else {
503 break;
504 }
505 }
506}
507
509{
510 if (!params.use) {
511 denoiser_.reset();
512 return;
513 }
514
515 GraphicsInteropDevice interop_device;
516 if (display_) {
517 interop_device = display_->graphics_interop_get_device();
518 }
519
520 Device *effective_denoise_device;
521 Device *cpu_fallback_device = cpu_device_.get();
522 const DenoiseParams effective_denoise_params = get_effective_denoise_params(
523 denoise_device_, cpu_fallback_device, params, interop_device, effective_denoise_device);
524
525 bool need_to_recreate_denoiser = false;
526 if (denoiser_) {
527 const DenoiseParams old_denoiser_params = denoiser_->get_params();
528
529 const bool is_cpu_denoising = old_denoiser_params.type == DENOISER_OPENIMAGEDENOISE &&
530 old_denoiser_params.use_gpu == false;
531 const bool requested_gpu_denoising = effective_denoise_params.type == DENOISER_OPTIX ||
532 (effective_denoise_params.type ==
534 effective_denoise_params.use_gpu == true);
535 if (requested_gpu_denoising && is_cpu_denoising &&
536 effective_denoise_device->info.type == DEVICE_CPU)
537 {
538 /* It won't be possible to use GPU denoising when according to user settings we have
539 * only CPU as available denoising device. So we just exiting early to avoid
540 * unnecessary denoiser recreation or parameters update. */
541 return;
542 }
543
544 const bool is_same_denoising_device_type = old_denoiser_params.use_gpu ==
545 effective_denoise_params.use_gpu;
546 /* Optix Denoiser is not supporting CPU devices, so use_gpu option is not
547 * shown in the UI and changes in the option value should not be checked. */
548 if (old_denoiser_params.type == effective_denoise_params.type &&
549 (is_same_denoising_device_type || effective_denoise_params.type == DENOISER_OPTIX))
550 {
551 denoiser_->set_params(effective_denoise_params);
552 }
553 else {
554 need_to_recreate_denoiser = true;
555 }
556 }
557 else {
558 /* if there is no denoiser and param.use is true, then we need to create it. */
559 need_to_recreate_denoiser = true;
560 }
561
562 if (need_to_recreate_denoiser) {
564 effective_denoise_device, cpu_fallback_device, effective_denoise_params, interop_device);
565
566 if (denoiser_) {
567 /* Only take into account the "immediate" cancel to have interactive rendering responding to
568 * navigation as quickly as possible, but allow to run denoiser after user hit Escape key
569 * while doing offline rendering. */
570 denoiser_->is_cancelled_cb = [this]() { return render_cancel_.is_requested; };
571 }
572 }
573
574 /* Use actual parameters, if available */
575 if (denoise_device_ && denoiser_) {
576 render_scheduler_.set_denoiser_params(denoiser_->get_params());
577 }
578 else {
579 render_scheduler_.set_denoiser_params(effective_denoise_params);
580 }
581}
582
584{
585 render_scheduler_.set_adaptive_sampling(adaptive_sampling);
586}
587
589{
590 if (!render_work.cryptomatte.postprocess) {
591 return;
592 }
593 LOG_DEBUG << "Perform cryptomatte work.";
594
595 parallel_for_each(path_trace_works_, [&](unique_ptr<PathTraceWork> &path_trace_work) {
596 path_trace_work->cryptomatte_postproces();
597 });
598}
599
600void PathTrace::denoise(const RenderWork &render_work)
601{
602 if (!render_work.tile.denoise) {
603 return;
604 }
605
606 if (!denoiser_) {
607 /* Denoiser was not configured, so nothing to do here. */
608 return;
609 }
610
611 LOG_DEBUG << "Perform denoising work.";
612
613 const double start_time = time_dt();
614
615 RenderBuffers *buffer_to_denoise = nullptr;
616 bool allow_inplace_modification = false;
617
618 Device *denoiser_device = denoiser_->get_denoiser_device();
619 if (path_trace_works_.size() > 1 && denoiser_device && !big_tile_denoise_work_) {
621 }
622
624 big_tile_denoise_work_->set_effective_buffer_params(render_state_.effective_big_tile_params,
625 render_state_.effective_big_tile_params,
626 render_state_.effective_big_tile_params);
627
628 buffer_to_denoise = big_tile_denoise_work_->get_render_buffers();
629 buffer_to_denoise->reset(render_state_.effective_big_tile_params);
630
631 copy_to_render_buffers(buffer_to_denoise);
632
633 allow_inplace_modification = true;
634 }
635 else {
636 DCHECK_EQ(path_trace_works_.size(), 1);
637
638 buffer_to_denoise = path_trace_works_.front()->get_render_buffers();
639 }
640
641 if (denoiser_->denoise_buffer(render_state_.effective_big_tile_params,
642 buffer_to_denoise,
644 allow_inplace_modification))
645 {
646 render_state_.has_denoised_result = true;
647 }
648
649 render_scheduler_.report_denoise_time(render_work, time_dt() - start_time);
650}
651
653 const bool has_volume)
654{
655 if (!has_volume || !render_scheduler_.volume_guiding_need_denoise()) {
656 return;
657 }
658
659 LOG_DEBUG << "Denoise volume guiding buffers.";
660
661 const double start_time = time_dt();
662
663 /* TODO: in the multi-GPU case, we can denoise on one device and copy to the rest, instead of
664 * denoising on each device separately. */
665 parallel_for_each(path_trace_works_, [&](unique_ptr<PathTraceWork> &path_trace_work) {
666 path_trace_work->denoise_volume_guiding_buffers();
667 });
668
669 render_scheduler_.report_volume_guiding_denoise_time(render_work, time_dt() - start_time);
670}
671
673{
674 output_driver_ = std::move(driver);
675}
676
678{
679 /* The display driver is the source of the drawing context which might be used by
680 * path trace works. Make sure there is no graphics interop using resources from
681 * the old display, as it might no longer be available after this call. */
683
684 if (driver) {
685 display_ = make_unique<PathTraceDisplay>(std::move(driver));
686 }
687 else {
688 display_ = nullptr;
689 }
690}
691
693{
694 if (display_) {
695 display_->zero();
696 }
697}
698
700{
701 if (!display_) {
702 return;
703 }
704
706}
707
709{
710 if (!display_) {
711 return;
712 }
713
714 display_->flush();
715}
716
717void PathTrace::update_display(const RenderWork &render_work)
718{
719 if (!render_work.display.update) {
720 return;
721 }
722
723 if (!display_ && !output_driver_) {
724 LOG_DEBUG << "Ignore display update.";
725 return;
726 }
727
728 if (full_params_.width == 0 || full_params_.height == 0) {
729 LOG_DEBUG << "Skipping PathTraceDisplay update due to 0 size of the render buffer.";
730 return;
731 }
732
733 const double start_time = time_dt();
734
735 if (output_driver_) {
736 LOG_DEBUG << "Invoke buffer update callback.";
737
738 const PathTraceTile tile(*this);
739 output_driver_->update_render_tile(tile);
740 }
741
742 if (display_) {
743 LOG_DEBUG << "Perform copy to GPUDisplay work.";
744
745 const int texture_width = render_state_.effective_big_tile_params.window_width;
746 const int texture_height = render_state_.effective_big_tile_params.window_height;
747 if (!display_->update_begin(texture_width, texture_height)) {
748 LOG_ERROR << "Error beginning GPUDisplay update.";
749 return;
750 }
751
752 const PassType pass_type = film_->get_display_pass();
753 const bool show_denoised = (render_work.display.use_denoised_result &&
755 is_volume_guiding_pass(pass_type);
756
757 const PassMode pass_mode = show_denoised ? PassMode::DENOISED : PassMode::NOISY;
758
759 /* TODO(sergey): When using multi-device rendering map the GPUDisplay once and copy data from
760 * all works in parallel. */
761 const int num_samples = get_num_samples_in_buffer();
762 if (big_tile_denoise_work_ && render_state_.has_denoised_result) {
763 big_tile_denoise_work_->copy_to_display(display_.get(), pass_mode, num_samples);
764 }
765 else {
766 for (auto &&path_trace_work : path_trace_works_) {
767 path_trace_work->copy_to_display(display_.get(), pass_mode, num_samples);
768 }
769 }
770
771 display_->update_end();
772 }
773
774 render_scheduler_.report_display_update_time(render_work, time_dt() - start_time);
775}
776
777void PathTrace::rebalance(const RenderWork &render_work)
778{
779 if (!render_work.rebalance) {
780 return;
781 }
782
783 const int num_works = path_trace_works_.size();
784
785 if (num_works == 1) {
786 LOG_DEBUG << "Ignoring rebalance work due to single device render.";
787 return;
788 }
789
790 const double start_time = time_dt();
791
793 LOG_DEBUG << "Perform rebalance work.";
794 LOG_DEBUG << "Per-device path tracing time (seconds):";
795 for (int i = 0; i < num_works; ++i) {
796 LOG_DEBUG << path_trace_works_[i]->get_device()->info.description << ": "
797 << work_balance_infos_[i].time_spent;
798 }
799 }
800
801 const bool did_rebalance = work_balance_do_rebalance(work_balance_infos_);
802
804 LOG_DEBUG << "Calculated per-device weights for works:";
805 for (int i = 0; i < num_works; ++i) {
806 LOG_DEBUG << path_trace_works_[i]->get_device()->info.description << ": "
807 << work_balance_infos_[i].weight;
808 }
809 }
810
811 if (!did_rebalance) {
812 LOG_DEBUG << "Balance in path trace works did not change.";
813 render_scheduler_.report_rebalance_time(render_work, time_dt() - start_time, false);
814 return;
815 }
816
817 RenderBuffers big_tile_cpu_buffers(cpu_device_.get());
818 big_tile_cpu_buffers.reset(render_state_.effective_big_tile_params);
819
820 copy_to_render_buffers(&big_tile_cpu_buffers);
821
822 render_state_.need_reset_params = true;
824
825 copy_from_render_buffers(&big_tile_cpu_buffers);
826
827 render_scheduler_.report_rebalance_time(render_work, time_dt() - start_time, true);
828}
829
831{
832 if (!render_work.tile.write) {
833 return;
834 }
835
836 LOG_DEBUG << "Write tile result.";
837
838 render_state_.tile_written = true;
839
840 const bool has_multiple_tiles = tile_manager_.has_multiple_tiles();
841
842 /* Write render tile result, but only if not using tiled rendering.
843 *
844 * Tiles are written to a file during rendering, and written to the software at the end
845 * of rendering (wither when all tiles are finished, or when rendering was requested to be
846 * canceled).
847 *
848 * Important thing is: tile should be written to the software via callback only once. */
849 if (!has_multiple_tiles) {
850 LOG_DEBUG << "Write tile result via buffer write callback.";
852 }
853 /* Write tile to disk, so that the render work's render buffer can be re-used for the next tile.
854 */
855 else {
856 LOG_DEBUG << "Write tile result to disk.";
858 }
859}
860
862{
863 if (!render_work.full.write) {
864 return;
865 }
866
867 LOG_DEBUG << "Handle full-frame render buffer work.";
868
869 if (!tile_manager_.has_written_tiles()) {
870 LOG_DEBUG << "No tiles on disk.";
871 return;
872 }
873
874 /* Make sure writing to the file is fully finished.
875 * This will include writing all possible missing tiles, ensuring validness of the file. */
876 tile_manager_.finish_write_tiles();
877
878 /* NOTE: The rest of full-frame post-processing (such as full-frame denoising) will be done after
879 * all scenes and layers are rendered by the Session (which happens after freeing Session memory,
880 * so that we never hold scene and full-frame buffer in memory at the same time). */
881}
882
884{
886
887 render_cancel_.is_requested = true;
888
889 while (render_cancel_.is_rendering) {
890 render_cancel_.condition.wait(lock);
891 }
892
893 render_cancel_.is_requested = false;
894}
895
897{
898 return render_scheduler_.get_num_rendered_samples();
899}
900
902{
903 if (render_cancel_.is_requested) {
904 return true;
905 }
906
907 if (progress_ != nullptr) {
908 if (progress_->get_cancel()) {
909 return true;
910 }
911 }
912
913 return false;
914}
915
917{
918 if (!output_driver_) {
919 return;
920 }
921
922 const PathTraceTile tile(*this);
923 output_driver_->write_render_tile(tile);
924}
925
927{
928 if (!device_scene_->data.bake.use) {
929 return;
930 }
931
932 if (!output_driver_) {
933 return;
934 }
935
936 /* Read buffers back from device. */
937 parallel_for_each(path_trace_works_, [&](unique_ptr<PathTraceWork> &path_trace_work) {
938 path_trace_work->copy_render_buffers_from_device();
939 });
940
941 /* Read (subset of) passes from output driver. */
942 const PathTraceTile tile(*this);
943 if (output_driver_->read_render_tile(tile)) {
944 /* Copy buffers to device again. */
945 parallel_for_each(path_trace_works_, [](unique_ptr<PathTraceWork> &path_trace_work) {
946 path_trace_work->copy_render_buffers_to_device();
947 });
948 }
949}
950
952{
953 /* Sample count pass is required to support per-tile partial results stored in the file. */
955
956 const int num_rendered_samples = render_scheduler_.get_num_rendered_samples();
957
958 if (num_rendered_samples == 0) {
959 /* The tile has zero samples, no need to write it. */
960 return;
961 }
962
963 /* Get access to the CPU-side render buffers of the current big tile. */
965 RenderBuffers big_tile_cpu_buffers(cpu_device_.get());
966
967 if (path_trace_works_.size() == 1) {
968 path_trace_works_[0]->copy_render_buffers_from_device();
969 buffers = path_trace_works_[0]->get_render_buffers();
970 }
971 else {
972 big_tile_cpu_buffers.reset(render_state_.effective_big_tile_params);
973 copy_to_render_buffers(&big_tile_cpu_buffers);
974
975 buffers = &big_tile_cpu_buffers;
976 }
977
978 if (!tile_manager_.write_tile(*buffers)) {
979 device_->set_error("Error writing tile to file");
980 }
981}
982
984{
985 if (progress_ != nullptr) {
986 const int2 tile_size = get_render_tile_size();
987 const uint64_t num_samples_added = uint64_t(tile_size.x) * tile_size.y *
988 render_work.path_trace.num_samples;
989 const int current_sample = render_work.path_trace.start_sample +
990 render_work.path_trace.num_samples -
991 render_work.path_trace.sample_offset;
992 progress_->add_samples(num_samples_added, current_sample);
993 }
994
995 if (progress_update_cb) {
997 }
998}
999
1000void PathTrace::progress_set_status(const string &status, const string &substatus)
1001{
1002 if (progress_ != nullptr) {
1003 progress_->set_status(status, substatus);
1004 }
1005}
1006
1008{
1009 parallel_for_each(path_trace_works_,
1010 [&render_buffers](unique_ptr<PathTraceWork> &path_trace_work) {
1011 path_trace_work->copy_to_render_buffers(render_buffers);
1012 });
1013 render_buffers->copy_to_device();
1014}
1015
1017{
1018 render_buffers->copy_from_device();
1019 parallel_for_each(path_trace_works_,
1020 [&render_buffers](unique_ptr<PathTraceWork> &path_trace_work) {
1021 path_trace_work->copy_from_render_buffers(render_buffers);
1022 });
1023}
1024
1026{
1027 if (full_frame_state_.render_buffers) {
1028 /* Full-frame buffer is always allocated on CPU. */
1029 return true;
1030 }
1031
1032 if (big_tile_denoise_work_ && render_state_.has_denoised_result) {
1033 return big_tile_denoise_work_->copy_render_buffers_from_device();
1034 }
1035
1036 bool success = true;
1037
1038 parallel_for_each(path_trace_works_, [&](unique_ptr<PathTraceWork> &path_trace_work) {
1039 if (!success) {
1040 return;
1041 }
1042 if (!path_trace_work->copy_render_buffers_from_device()) {
1043 success = false;
1044 }
1045 });
1046
1047 return success;
1048}
1049
1051{
1052 string result;
1053
1054 if (!buffers.params.layer.empty()) {
1055 result += string(buffers.params.layer);
1056 }
1057
1058 if (!buffers.params.view.empty()) {
1059 if (!result.empty()) {
1060 result += ", ";
1061 }
1062 result += string(buffers.params.view);
1063 }
1064
1065 return result;
1066}
1067
1069{
1070 LOG_DEBUG << "Processing full frame buffer file " << filename;
1071
1072 progress_set_status("Reading full buffer from disk");
1073
1074 RenderBuffers full_frame_buffers(cpu_device_.get());
1075
1076 DenoiseParams denoise_params;
1077 if (!tile_manager_.read_full_buffer_from_disk(filename, &full_frame_buffers, &denoise_params)) {
1078 const string error_message = "Error reading tiles from file";
1079 if (progress_) {
1080 progress_->set_error(error_message);
1081 progress_->set_cancel(error_message);
1082 }
1083 else {
1084 LOG_ERROR << error_message;
1085 }
1086 return;
1087 }
1088
1089 const string layer_view_name = get_layer_view_name(full_frame_buffers);
1090
1091 render_state_.has_denoised_result = false;
1092
1093 if (denoise_params.use && denoiser_ && !progress_->get_cancel()) {
1094 progress_set_status(layer_view_name, "Denoising");
1095
1096 /* If GPU should be used is not based on file metadata. */
1097 denoise_params.use_gpu = render_scheduler_.is_denoiser_gpu_used();
1098
1099 /* Re-use the denoiser as much as possible, avoiding possible device re-initialization.
1100 *
1101 * It will not conflict with the regular rendering as:
1102 * - Rendering is supposed to be finished here.
1103 * - The next rendering will go via Session's `run_update_for_next_iteration` which will
1104 * ensure proper denoiser is used. */
1105 set_denoiser_params(denoise_params);
1106
1107 /* Number of samples doesn't matter too much, since the samples count pass will be used. */
1108 denoiser_->denoise_buffer(full_frame_buffers.params, &full_frame_buffers, 0, false);
1109
1110 render_state_.has_denoised_result = true;
1111 }
1112
1113 full_frame_state_.render_buffers = &full_frame_buffers;
1114
1115 progress_set_status(layer_view_name, "Finishing");
1116
1117 /* Write the full result pretending that there is a single tile.
1118 * Requires some state change, but allows to use same communication API with the software. */
1120
1121 full_frame_state_.render_buffers = nullptr;
1122}
1123
1125{
1126 if (full_frame_state_.render_buffers) {
1127 return full_frame_state_.render_buffers->params.samples;
1128 }
1129
1130 return render_scheduler_.get_num_rendered_samples();
1131}
1132
1134 const PassAccessor::Destination &destination)
1135{
1136 if (full_frame_state_.render_buffers) {
1137 return pass_accessor.get_render_tile_pixels(full_frame_state_.render_buffers, destination);
1138 }
1139
1140 if (big_tile_denoise_work_ && render_state_.has_denoised_result) {
1141 /* Only use the big tile denoised buffer to access the denoised passes.
1142 * The guiding passes are allowed to be modified in-place for the needs of the denoiser,
1143 * so copy those from the original devices buffers. */
1144 if (pass_accessor.get_pass_access_info().mode == PassMode::DENOISED) {
1145 return big_tile_denoise_work_->get_render_tile_pixels(pass_accessor, destination);
1146 }
1147 }
1148
1149 bool success = true;
1150
1151 parallel_for_each(path_trace_works_, [&](unique_ptr<PathTraceWork> &path_trace_work) {
1152 if (!success) {
1153 return;
1154 }
1155 if (!path_trace_work->get_render_tile_pixels(pass_accessor, destination)) {
1156 success = false;
1157 }
1158 });
1159
1160 return success;
1161}
1162
1164 const PassAccessor::Source &source)
1165{
1166 bool success = true;
1167
1168 parallel_for_each(path_trace_works_, [&](unique_ptr<PathTraceWork> &path_trace_work) {
1169 if (!success) {
1170 return;
1171 }
1172 if (!path_trace_work->set_render_tile_pixels(pass_accessor, source)) {
1173 success = false;
1174 }
1175 });
1176
1177 return success;
1178}
1179
1181{
1182 if (full_frame_state_.render_buffers) {
1183 return make_int2(full_frame_state_.render_buffers->params.window_width,
1184 full_frame_state_.render_buffers->params.window_height);
1185 }
1186
1187 const Tile &tile = tile_manager_.get_current_tile();
1188 return make_int2(tile.window_width, tile.window_height);
1189}
1190
1192{
1193 if (full_frame_state_.render_buffers) {
1194 return make_int2(0, 0);
1195 }
1196
1197 const Tile &tile = tile_manager_.get_current_tile();
1198 return make_int2(tile.x + tile.window_x, tile.y + tile.window_y);
1199}
1200
1202{
1203 return tile_manager_.get_size();
1204}
1205
1207{
1208 if (full_frame_state_.render_buffers) {
1209 return full_frame_state_.render_buffers->params;
1210 }
1211
1212 return big_tile_params_;
1213}
1214
1216{
1217 return render_state_.has_denoised_result;
1218}
1219
1221{
1222 /* Destroy any GPU resource which was used for graphics interop.
1223 * Need to have access to the PathTraceDisplay as it is the only source of drawing context which
1224 * is used for interop. */
1225 if (display_) {
1226 for (auto &&path_trace_work : path_trace_works_) {
1227 path_trace_work->destroy_gpu_resources(display_.get());
1228 }
1229
1231 big_tile_denoise_work_->destroy_gpu_resources(display_.get());
1232 }
1233 }
1234}
1235
1236/* --------------------------------------------------------------------
1237 * Report generation.
1238 */
1239
1240static const char *device_type_for_description(const DeviceType type)
1241{
1242 switch (type) {
1243 case DEVICE_NONE:
1244 return "None";
1245
1246 case DEVICE_CPU:
1247 return "CPU";
1248 case DEVICE_CUDA:
1249 return "CUDA";
1250 case DEVICE_OPTIX:
1251 return "OptiX";
1252 case DEVICE_HIP:
1253 return "HIP";
1254 case DEVICE_HIPRT:
1255 return "HIPRT";
1256 case DEVICE_ONEAPI:
1257 return "oneAPI";
1258 case DEVICE_DUMMY:
1259 return "Dummy";
1260 case DEVICE_MULTI:
1261 return "Multi";
1262 case DEVICE_METAL:
1263 return "Metal";
1264 }
1265
1266 return "UNKNOWN";
1267}
1268
1269/* Construct description of the device which will appear in the full report. */
1270/* TODO(sergey): Consider making it more reusable utility. */
1271static string full_device_info_description(const DeviceInfo &device_info)
1272{
1273 string full_description = device_info.description;
1274
1275 full_description += " (" + string(device_type_for_description(device_info.type)) + ")";
1276
1277 if (device_info.display_device) {
1278 full_description += " (display)";
1279 }
1280
1281 if (device_info.type == DEVICE_CPU) {
1282 full_description += " (" + to_string(device_info.cpu_threads) + " threads)";
1283 }
1284
1285 full_description += " [" + device_info.id + "]";
1286
1287 return full_description;
1288}
1289
1290/* Construct string which will contain information about devices, possibly multiple of the devices.
1291 *
1292 * In the simple case the result looks like:
1293 *
1294 * Message: Full Device Description
1295 *
1296 * If there are multiple devices then the result looks like:
1297 *
1298 * Message: Full First Device Description
1299 * Full Second Device Description
1300 *
1301 * Note that the newlines are placed in a way so that the result can be easily concatenated to the
1302 * full report. */
1303static string device_info_list_report(const string &message, const DeviceInfo &device_info)
1304{
1305 string result = "\n" + message + ": ";
1306 const string pad(message.length() + 2, ' ');
1307
1308 if (device_info.multi_devices.empty()) {
1309 result += full_device_info_description(device_info) + "\n";
1310 return result;
1311 }
1312
1313 bool is_first = true;
1314 for (const DeviceInfo &sub_device_info : device_info.multi_devices) {
1315 if (!is_first) {
1316 result += pad;
1317 }
1318
1319 result += full_device_info_description(sub_device_info) + "\n";
1320
1321 is_first = false;
1322 }
1323
1324 return result;
1325}
1326
1327static string path_trace_devices_report(const vector<unique_ptr<PathTraceWork>> &path_trace_works)
1328{
1329 DeviceInfo device_info;
1330 device_info.type = DEVICE_MULTI;
1331
1332 for (auto &&path_trace_work : path_trace_works) {
1333 device_info.multi_devices.push_back(path_trace_work->get_device()->info);
1334 }
1335
1336 return device_info_list_report("Path tracing on", device_info);
1337}
1338
1339static string denoiser_device_report(const Denoiser *denoiser)
1340{
1341 if (!denoiser) {
1342 return "";
1343 }
1344
1345 if (!denoiser->get_params().use) {
1346 return "";
1347 }
1348
1349 const Device *denoiser_device = denoiser->get_denoiser_device();
1350 if (!denoiser_device) {
1351 return "";
1352 }
1353
1354 return device_info_list_report("Denoising on", denoiser_device->info);
1355}
1356
1358{
1359 string result = "Full path tracing report:\n";
1360
1363
1364 /* Report from the render scheduler, which includes:
1365 * - Render mode (interactive, offline, headless)
1366 * - Adaptive sampling and denoiser parameters
1367 * - Breakdown of timing. */
1368 result += render_scheduler_.full_report();
1369
1370 return result;
1371}
1372
1373void PathTrace::set_guiding_params(const GuidingParams &guiding_params, const bool reset)
1374{
1375#if defined(WITH_PATH_GUIDING)
1376 if (guiding_params_.modified(guiding_params)) {
1377 guiding_params_ = guiding_params;
1378
1379# if !(OPENPGL_VERSION_MAJOR == 0 && OPENPGL_VERSION_MINOR <= 5)
1380# define OPENPGL_USE_FIELD_CONFIG
1381# endif
1382
1383 if (guiding_params_.use) {
1384# ifdef OPENPGL_USE_FIELD_CONFIG
1385 openpgl::cpp::FieldConfig field_config;
1386# else
1387 PGLFieldArguments field_args;
1388# endif
1389 switch (guiding_params_.type) {
1390 default:
1391 /* Parallax-aware von Mises-Fisher mixture models. */
1393# ifdef OPENPGL_USE_FIELD_CONFIG
1394 field_config.Init(
1395 PGL_SPATIAL_STRUCTURE_TYPE::PGL_SPATIAL_STRUCTURE_KDTREE,
1396 PGL_DIRECTIONAL_DISTRIBUTION_TYPE::PGL_DIRECTIONAL_DISTRIBUTION_PARALLAX_AWARE_VMM,
1397 guiding_params.deterministic);
1398# else
1399 pglFieldArgumentsSetDefaults(
1400 field_args,
1401 PGL_SPATIAL_STRUCTURE_TYPE::PGL_SPATIAL_STRUCTURE_KDTREE,
1402 PGL_DIRECTIONAL_DISTRIBUTION_TYPE::PGL_DIRECTIONAL_DISTRIBUTION_PARALLAX_AWARE_VMM);
1403# endif
1404 break;
1405 }
1406 /* Directional quad-trees. */
1408# ifdef OPENPGL_USE_FIELD_CONFIG
1409 field_config.Init(
1410 PGL_SPATIAL_STRUCTURE_TYPE::PGL_SPATIAL_STRUCTURE_KDTREE,
1411 PGL_DIRECTIONAL_DISTRIBUTION_TYPE::PGL_DIRECTIONAL_DISTRIBUTION_QUADTREE,
1412 guiding_params.deterministic);
1413# else
1414 pglFieldArgumentsSetDefaults(
1415 field_args,
1416 PGL_SPATIAL_STRUCTURE_TYPE::PGL_SPATIAL_STRUCTURE_KDTREE,
1417 PGL_DIRECTIONAL_DISTRIBUTION_TYPE::PGL_DIRECTIONAL_DISTRIBUTION_QUADTREE);
1418# endif
1419 break;
1420 }
1421 /* von Mises-Fisher mixture models. */
1422 case GUIDING_TYPE_VMM: {
1423# ifdef OPENPGL_USE_FIELD_CONFIG
1424 field_config.Init(PGL_SPATIAL_STRUCTURE_TYPE::PGL_SPATIAL_STRUCTURE_KDTREE,
1425 PGL_DIRECTIONAL_DISTRIBUTION_TYPE::PGL_DIRECTIONAL_DISTRIBUTION_VMM,
1426 guiding_params.deterministic);
1427# else
1428 pglFieldArgumentsSetDefaults(
1429 field_args,
1430 PGL_SPATIAL_STRUCTURE_TYPE::PGL_SPATIAL_STRUCTURE_KDTREE,
1431 PGL_DIRECTIONAL_DISTRIBUTION_TYPE::PGL_DIRECTIONAL_DISTRIBUTION_VMM);
1432# endif
1433 break;
1434 }
1435 }
1436# ifdef OPENPGL_USE_FIELD_CONFIG
1437 field_config.SetSpatialStructureArgMaxDepth(16);
1438# else
1439 field_args.deterministic = guiding_params.deterministic;
1440 reinterpret_cast<PGLKDTreeArguments *>(field_args.spatialSturctureArguments)->maxDepth = 16;
1441# endif
1442 openpgl::cpp::Device *guiding_device = static_cast<openpgl::cpp::Device *>(
1443 device_->get_guiding_device());
1444 if (guiding_device) {
1445 guiding_sample_data_storage_ = make_unique<openpgl::cpp::SampleStorage>();
1446# ifdef OPENPGL_USE_FIELD_CONFIG
1447 guiding_field_ = make_unique<openpgl::cpp::Field>(guiding_device, field_config);
1448# else
1449 guiding_field_ = make_unique<openpgl::cpp::Field>(guiding_device, field_args);
1450# endif
1451 }
1452 else {
1453 guiding_sample_data_storage_ = nullptr;
1454 guiding_field_ = nullptr;
1455 }
1456 }
1457 else {
1458 guiding_sample_data_storage_ = nullptr;
1459 guiding_field_ = nullptr;
1460 }
1461 }
1462 else if (reset) {
1463 if (guiding_field_) {
1464 guiding_field_->Reset();
1465 }
1466 }
1467#else
1468 (void)guiding_params;
1469 (void)reset;
1470#endif
1471}
1472
1474{
1475#if defined(WITH_PATH_GUIDING)
1476 const bool train = (guiding_params_.training_samples == 0) ||
1477 (guiding_field_->GetIteration() < guiding_params_.training_samples);
1478
1479 for (auto &&path_trace_work : path_trace_works_) {
1480 path_trace_work->guiding_init_kernel_globals(
1481 guiding_field_.get(), guiding_sample_data_storage_.get(), train);
1482 }
1483
1484 if (train) {
1485 /* For training the guiding distribution we need to force the number of samples
1486 * per update to be limited, for reproducible results and reasonable training size.
1487 *
1488 * Idea: we could stochastically discard samples with a probability of 1/num_samples_per_update
1489 * we can then update only after the num_samples_per_update iterations are rendered. */
1490 render_scheduler_.set_limit_samples_per_update(4);
1491 }
1492 else {
1493 render_scheduler_.set_limit_samples_per_update(0);
1494 }
1495#endif
1496}
1497
1499{
1500#if defined(WITH_PATH_GUIDING)
1501 LOG_DEBUG << "Update path guiding structures";
1502
1503 LOG_TRACE << "Number of surface samples: " << guiding_sample_data_storage_->GetSizeSurface();
1504 LOG_TRACE << "Number of volume samples: " << guiding_sample_data_storage_->GetSizeVolume();
1505
1506 const size_t num_valid_samples = guiding_sample_data_storage_->GetSizeSurface() +
1507 guiding_sample_data_storage_->GetSizeVolume();
1508
1509 /* we wait until we have at least 1024 samples */
1510 if (num_valid_samples >= 1024) {
1511 guiding_field_->Update(*guiding_sample_data_storage_);
1512 guiding_update_count++;
1513
1514 LOG_TRACE << "Path guiding field valid: " << guiding_field_->Validate();
1515
1516 guiding_sample_data_storage_->Clear();
1517 }
1518#endif
1519}
1520
unsigned int uint
ATOMIC_INLINE unsigned int atomic_add_and_fetch_u(unsigned int *p, unsigned int x)
int pad[32 - sizeof(int)]
volatile int lock
unsigned long long int uint64_t
int pass_stride
Definition buffers.h:92
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
DenoiserType type
Definition denoise.h:59
bool use_gpu
Definition denoise.h:73
NODE_DECLARE bool use
Definition denoise.h:56
static unique_ptr< Denoiser > create(Device *denoiser_device, Device *cpu_fallback_device, const DenoiseParams &params, const GraphicsInteropDevice &interop_device)
Definition denoiser.cpp:148
Device * get_denoiser_device() const
Definition denoiser.cpp:268
const DenoiseParams & get_params() const
Definition denoiser.cpp:230
vector< DeviceInfo > multi_devices
bool display_device
DeviceType type
string description
Profiler & profiler
Stats & stats
DeviceInfo info
bool have_error()
Definition film.h:29
bool get_render_tile_pixels(const RenderBuffers *render_buffers, const Destination &destination) const
const PassAccessInfo & get_pass_access_info() const
RenderBuffers * get_render_buffers()
static unique_ptr< PathTraceWork > create(Device *device, Film *film, DeviceScene *device_scene, const bool *cancel_requested_flag)
void set_effective_buffer_params(const BufferParams &effective_full_params, const BufferParams &effective_big_tile_params, const BufferParams &effective_buffer_params)
virtual void render_samples(RenderStatistics &statistics, const int start_sample, const int samples_num, const int sample_offset)=0
Device * get_device() const
void device_free()
Progress * progress_
Definition path_trace.h:336
void denoise(const RenderWork &render_work)
int2 get_render_tile_size() const
void finalize_full_buffer_on_disk(const RenderWork &render_work)
std::function< void(void)> progress_update_cb
Definition path_trace.h:185
void process_full_buffer_from_disk(string_view filename)
void tile_buffer_write()
Device * device_
Definition path_trace.h:255
unique_ptr< Denoiser > denoiser_
Definition path_trace.h:288
void update_effective_work_buffer_params(const RenderWork &render_work)
BufferParams full_params_
Definition path_trace.h:284
void rebalance(const RenderWork &render_work)
bool is_cancel_requested()
struct PathTrace::@134371127017210132007075065004246317237136077123 render_state_
void init_render_buffers(const RenderWork &render_work)
void copy_to_render_buffers(RenderBuffers *render_buffers)
void destroy_gpu_resources()
void set_progress(Progress *progress)
vector< unique_ptr< PathTraceWork > > path_trace_works_
Definition path_trace.h:278
void copy_from_render_buffers(RenderBuffers *render_buffers)
Device * denoise_device_
Definition path_trace.h:259
void set_denoiser_params(const DenoiseParams &params)
struct PathTrace::@045127216101200035253035102154226266260300121335 full_frame_state_
void progress_update_if_needed(const RenderWork &render_work)
void update_display(const RenderWork &render_work)
void progress_set_status(const string &status, const string &substatus="")
void set_adaptive_sampling(const AdaptiveSampling &adaptive_sampling)
void set_output_driver(unique_ptr< OutputDriver > driver)
void reset(const BufferParams &full_params, const BufferParams &big_tile_params, bool reset_rendering)
DeviceScene * device_scene_
Definition path_trace.h:265
bool ready_to_reset()
bool get_render_tile_pixels(const PassAccessor &pass_accessor, const PassAccessor::Destination &destination)
void cancel()
void guiding_update_structures()
PathTrace(Device *device, Device *denoise_device, Film *film, DeviceScene *device_scene, RenderScheduler &render_scheduler, TileManager &tile_manager)
void zero_display()
void guiding_prepare_structures()
int2 get_render_tile_offset() const
unique_ptr< Device > cpu_device_
Definition path_trace.h:262
void denoise_volume_guiding_buffers(const RenderWork &render_work, const bool has_volume)
void set_guiding_params(const GuidingParams &params, const bool reset)
void tile_buffer_write_to_disk()
void set_display_driver(unique_ptr< DisplayDriver > driver)
void path_trace(RenderWork &render_work)
void render_pipeline(RenderWork render_work)
unique_ptr< OutputDriver > output_driver_
Definition path_trace.h:274
unique_ptr< PathTraceDisplay > display_
Definition path_trace.h:271
vector< WorkBalanceInfo > work_balance_infos_
Definition path_trace.h:281
RenderBuffers * render_buffers
Definition path_trace.h:358
bool copy_render_tile_from_device()
bool has_denoised_result
Definition path_trace.h:328
bool did_draw_after_reset_
Definition path_trace.h:354
void render_init_kernel_execution()
int2 get_render_size() const
struct PathTrace::@264204132215367063060273366367103232030034067245 render_cancel_
TileManager & tile_manager_
Definition path_trace.h:268
unique_ptr< PathTraceWork > big_tile_denoise_work_
Definition path_trace.h:291
void update_work_buffer_params_if_needed(const RenderWork &render_work)
void alloc_work_memory()
BufferParams big_tile_params_
Definition path_trace.h:285
void draw()
void write_tile_buffer(const RenderWork &render_work)
void tile_buffer_read()
bool set_render_tile_pixels(PassAccessor &pass_accessor, const PassAccessor::Source &source)
void render(const RenderWork &render_work)
Film * film_
Definition path_trace.h:264
string full_report() const
void adaptive_sample(RenderWork &render_work)
void load_kernels()
const BufferParams & get_render_tile_params() const
int resolution_divider
Definition path_trace.h:322
int get_num_render_tile_samples() const
void flush_display()
RenderScheduler & render_scheduler_
Definition path_trace.h:267
int get_num_samples_in_buffer()
void cryptomatte_postprocess(const RenderWork &render_work)
void update_allocated_work_buffer_params()
BufferParams params
Definition buffers.h:155
void reset(const BufferParams &params)
Definition buffers.cpp:271
void set_need_schedule_rebalance(bool need_schedule_rebalance)
struct RenderWork::@274302037211333357242061375066056076354104241257 cryptomatte
bool use_denoised_result
struct RenderWork::@143272241044374345203044061122063322004171016113 tile
bool init_render_buffers
struct RenderWork::@332011177057136214007215372066145235154020032310 path_trace
struct RenderWork::@165363207370354371245137325023006326210217053004 adaptive_sampling
struct RenderWork::@226364033061301324161311275016362103243040016376 display
struct RenderWork::@200361311027272113377377324353154145102345065344 full
@ DENOISER_OPTIX
Definition denoise.h:12
@ DENOISER_OPENIMAGEDENOISE
Definition denoise.h:13
DenoiseParams get_effective_denoise_params(Device *denoiser_device, Device *cpu_fallback_device, const DenoiseParams &params, const GraphicsInteropDevice &interop_device, Device *&single_denoiser_device)
Definition denoiser.cpp:105
void device_cpu_info(vector< DeviceInfo > &devices)
CCL_NAMESPACE_BEGIN unique_ptr< Device > device_cpu_create(const DeviceInfo &info, Stats &stats, Profiler &profiler, bool headless)
#define PASS_UNUSED
#define CCL_NAMESPACE_END
DeviceType
@ DEVICE_DUMMY
@ DEVICE_NONE
@ DEVICE_METAL
@ DEVICE_MULTI
@ DEVICE_CUDA
@ DEVICE_CPU
@ DEVICE_HIPRT
@ DEVICE_OPTIX
@ DEVICE_HIP
@ DEVICE_ONEAPI
ccl_device_forceinline int2 make_int2(const int x, const int y)
static const char * to_string(const Interpolation &interp)
Definition gl_shader.cc:103
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
const ccl_global KernelWorkTile * tile
PassType
@ PASS_SAMPLE_COUNT
@ GUIDING_TYPE_VMM
@ GUIDING_TYPE_DIRECTIONAL_QUAD_TREE
@ GUIDING_TYPE_PARALLAX_AWARE_VMM
#define DCHECK(expression)
Definition log.h:135
#define DCHECK_EQ(a, b)
Definition log.h:144
#define LOG_DEBUG
Definition log.h:107
#define LOG_IS_ON(level)
Definition log.h:113
@ LOG_LEVEL_DEBUG
Definition log.h:26
#define LOG_ERROR
Definition log.h:101
#define DCHECK_NE(a, b)
Definition log.h:143
#define LOG_INFO
Definition log.h:106
#define LOG_TRACE
Definition log.h:108
ccl_device_inline uint next_power_of_two(const uint x)
Definition math_base.h:786
bool is_volume_guiding_pass(const PassType pass_type)
Definition pass.cpp:474
PassMode
Definition pass.h:20
@ DENOISED
Definition pass.h:22
@ NOISY
Definition pass.h:21
static string device_info_list_report(const string &message, const DeviceInfo &device_info)
static void foreach_sliced_buffer_params(const vector< unique_ptr< PathTraceWork > > &path_trace_works, const vector< WorkBalanceInfo > &work_balance_infos, const BufferParams &buffer_params, const int overscan, const Callback &callback)
static string path_trace_devices_report(const vector< unique_ptr< PathTraceWork > > &path_trace_works)
static BufferParams scale_buffer_params(const BufferParams &params, const int resolution_divider)
static string full_device_info_description(const DeviceInfo &device_info)
static string get_layer_view_name(const RenderBuffers &buffers)
static const char * device_type_for_description(const DeviceType type)
static string denoiser_device_report(const Denoiser *denoiser)
const int status
#define min(a, b)
Definition sort.cc:36
static void thread_capture_fp_settings()
Definition tbb.h:38
i
Definition text_draw.cc:230
max
Definition text_draw.cc:251
std::unique_lock< std::mutex > thread_scoped_lock
Definition thread.h:28
CCL_NAMESPACE_BEGIN double time_dt()
Definition time.cpp:47
char * buffers[2]
CCL_NAMESPACE_BEGIN void work_balance_do_initial(vector< WorkBalanceInfo > &work_balance_infos)
bool work_balance_do_rebalance(vector< WorkBalanceInfo > &work_balance_infos)